mark-improving-agent 2.2.3 → 2.2.5
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/collaboration/index.js +2 -0
- package/dist/core/collaboration/mcp-protocol.js +392 -0
- package/dist/core/collaboration/peer-review.js +265 -0
- package/dist/core/identity/identity-continuity.js +301 -0
- package/dist/core/identity/index.js +1 -0
- package/dist/core/memory/context-fragmentation.js +308 -0
- package/dist/core/memory/hybrid-search.js +177 -0
- package/dist/core/memory/index.js +2 -0
- package/dist/core/ontology/concept-graph.js +437 -0
- package/dist/core/ontology/index.js +18 -0
- package/dist/index.js +3 -0
- package/dist/version.js +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hybrid Memory Search
|
|
3
|
+
*
|
|
4
|
+
* Combines vector similarity search with BM25 keyword matching and knowledge graph traversal
|
|
5
|
+
* Based on Dakera AI's hybrid search architecture (87.8% LoCoMo accuracy)
|
|
6
|
+
*
|
|
7
|
+
* @module core/memory
|
|
8
|
+
* @fileoverview Hybrid search combining vector + BM25 + knowledge graph for superior recall
|
|
9
|
+
*/
|
|
10
|
+
import { createLogger } from '../../utils/logger.js';
|
|
11
|
+
const logger = createLogger('HybridSearch');
|
|
12
|
+
/**
|
|
13
|
+
* Default hybrid search configuration
|
|
14
|
+
*/
|
|
15
|
+
export const DEFAULT_HYBRID_CONFIG = {
|
|
16
|
+
vectorWeight: 0.5,
|
|
17
|
+
bm25Weight: 0.3,
|
|
18
|
+
kgWeight: 0.2,
|
|
19
|
+
bm25: { k1: 1.5, b: 0.75 },
|
|
20
|
+
minScore: 0.1,
|
|
21
|
+
maxResults: 20,
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* Tokenizer for BM25
|
|
25
|
+
*/
|
|
26
|
+
function tokenize(text) {
|
|
27
|
+
return text
|
|
28
|
+
.toLowerCase()
|
|
29
|
+
.replace(/[^\w\s]/g, ' ')
|
|
30
|
+
.split(/\s+/)
|
|
31
|
+
.filter(token => token.length > 2);
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Create a BM25 index from memory entries
|
|
35
|
+
*/
|
|
36
|
+
export function createBM25Index(entries) {
|
|
37
|
+
const index = {
|
|
38
|
+
docLengths: new Map(),
|
|
39
|
+
termDocFreq: new Map(),
|
|
40
|
+
avgDocLength: 0,
|
|
41
|
+
totalDocs: entries.length,
|
|
42
|
+
invertedIndex: new Map(),
|
|
43
|
+
};
|
|
44
|
+
let totalLength = 0;
|
|
45
|
+
for (const entry of entries) {
|
|
46
|
+
const tokens = tokenize(entry.content);
|
|
47
|
+
const docLength = tokens.length;
|
|
48
|
+
index.docLengths.set(entry.id, docLength);
|
|
49
|
+
totalLength += docLength;
|
|
50
|
+
// Count term frequencies
|
|
51
|
+
const termFreq = new Map();
|
|
52
|
+
for (const token of tokens) {
|
|
53
|
+
termFreq.set(token, (termFreq.get(token) ?? 0) + 1);
|
|
54
|
+
if (!index.invertedIndex.has(token)) {
|
|
55
|
+
index.invertedIndex.set(token, new Map());
|
|
56
|
+
}
|
|
57
|
+
const posting = index.invertedIndex.get(token);
|
|
58
|
+
posting.set(entry.id, termFreq.get(token));
|
|
59
|
+
}
|
|
60
|
+
// Update document frequency
|
|
61
|
+
for (const token of new Set(tokens)) {
|
|
62
|
+
index.termDocFreq.set(token, (index.termDocFreq.get(token) ?? 0) + 1);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
index.avgDocLength = totalLength / Math.max(entries.length, 1);
|
|
66
|
+
return index;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Calculate BM25 score for a single document
|
|
70
|
+
*/
|
|
71
|
+
export function bm25Score(index, docId, queryTokens, config) {
|
|
72
|
+
const docLength = index.docLengths.get(docId) ?? 0;
|
|
73
|
+
let score = 0;
|
|
74
|
+
for (const token of queryTokens) {
|
|
75
|
+
const tf = index.invertedIndex.get(token)?.get(docId) ?? 0;
|
|
76
|
+
if (tf === 0)
|
|
77
|
+
continue;
|
|
78
|
+
const df = index.termDocFreq.get(token) ?? 0;
|
|
79
|
+
if (df === 0)
|
|
80
|
+
continue;
|
|
81
|
+
const idf = Math.log((index.totalDocs - df + 0.5) / (df + 0.5) + 1);
|
|
82
|
+
const tfComponent = (tf * (config.k1 + 1)) / (tf + config.k1 * (1 - config.b + config.b * (docLength / index.avgDocLength)));
|
|
83
|
+
score += idf * tfComponent;
|
|
84
|
+
}
|
|
85
|
+
return score;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Normalize BM25 scores to 0-1 range
|
|
89
|
+
*/
|
|
90
|
+
export function normalizeBM25Scores(scores) {
|
|
91
|
+
const maxScore = Math.max(...Array.from(scores.values()), 1);
|
|
92
|
+
const normalized = new Map();
|
|
93
|
+
for (const [docId, score] of scores) {
|
|
94
|
+
normalized.set(docId, score / maxScore);
|
|
95
|
+
}
|
|
96
|
+
return normalized;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Create a hybrid search engine
|
|
100
|
+
*/
|
|
101
|
+
export function createHybridSearchEngine(initialEntries = [], config = {}) {
|
|
102
|
+
const fullConfig = {
|
|
103
|
+
...DEFAULT_HYBRID_CONFIG,
|
|
104
|
+
...config,
|
|
105
|
+
bm25: { ...DEFAULT_HYBRID_CONFIG.bm25, ...config.bm25 },
|
|
106
|
+
};
|
|
107
|
+
let bm25Index = createBM25Index(initialEntries);
|
|
108
|
+
function rebuildIndex(entries) {
|
|
109
|
+
logger.info(`Rebuilding BM25 index with ${entries.length} documents`);
|
|
110
|
+
bm25Index = createBM25Index(entries);
|
|
111
|
+
}
|
|
112
|
+
function search(query, entries, options) {
|
|
113
|
+
const searchConfig = {
|
|
114
|
+
...fullConfig,
|
|
115
|
+
...options?.config,
|
|
116
|
+
};
|
|
117
|
+
// Tokenize query for BM25
|
|
118
|
+
const queryTokens = tokenize(query);
|
|
119
|
+
// Calculate BM25 scores for all entries
|
|
120
|
+
const bm25Scores = new Map();
|
|
121
|
+
for (const entry of entries) {
|
|
122
|
+
const score = bm25Score(bm25Index, entry.id, queryTokens, searchConfig.bm25);
|
|
123
|
+
if (score > 0) {
|
|
124
|
+
bm25Scores.set(entry.id, score);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
const normalizedBM25 = normalizeBM25Scores(bm25Scores);
|
|
128
|
+
// Calculate KG scores based on connection count
|
|
129
|
+
const kgScores = new Map();
|
|
130
|
+
if (options?.kgConnections) {
|
|
131
|
+
const maxConnections = Math.max(...Array.from(options.kgConnections.values()).map(arr => arr.length), 1);
|
|
132
|
+
for (const entry of entries) {
|
|
133
|
+
const connections = options.kgConnections.get(entry.id)?.length ?? 0;
|
|
134
|
+
kgScores.set(entry.id, connections / maxConnections);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
// Combine scores
|
|
138
|
+
const combinedResults = new Map();
|
|
139
|
+
for (const entry of entries) {
|
|
140
|
+
const vectorScore = options?.vectorScores?.get(entry.id) ?? 0;
|
|
141
|
+
const bm25Score = normalizedBM25.get(entry.id) ?? 0;
|
|
142
|
+
const kgScore = kgScores.get(entry.id) ?? 0;
|
|
143
|
+
// Weighted combination
|
|
144
|
+
const combinedScore = (vectorScore * searchConfig.vectorWeight) +
|
|
145
|
+
(bm25Score * searchConfig.bm25Weight) +
|
|
146
|
+
(kgScore * searchConfig.kgWeight);
|
|
147
|
+
if (combinedScore >= searchConfig.minScore) {
|
|
148
|
+
combinedResults.set(entry.id, {
|
|
149
|
+
entry,
|
|
150
|
+
score: combinedScore,
|
|
151
|
+
combinedScore,
|
|
152
|
+
vectorScore,
|
|
153
|
+
bm25Score,
|
|
154
|
+
kgScore,
|
|
155
|
+
reason: 'semantic',
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
// Sort by combined score
|
|
160
|
+
const results = Array.from(combinedResults.values())
|
|
161
|
+
.sort((a, b) => b.combinedScore - a.combinedScore)
|
|
162
|
+
.slice(0, searchConfig.maxResults);
|
|
163
|
+
logger.debug(`Hybrid search for "${query}": ${results.length} results`);
|
|
164
|
+
return results;
|
|
165
|
+
}
|
|
166
|
+
function getStats() {
|
|
167
|
+
return {
|
|
168
|
+
indexedDocs: bm25Index.totalDocs,
|
|
169
|
+
avgDocLength: Math.round(bm25Index.avgDocLength),
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
return {
|
|
173
|
+
search,
|
|
174
|
+
rebuildIndex,
|
|
175
|
+
getStats,
|
|
176
|
+
};
|
|
177
|
+
}
|
|
@@ -7,3 +7,5 @@ export * from './dream-consolidation.js';
|
|
|
7
7
|
export * from './spaced-repetition.js';
|
|
8
8
|
export * from './hopfield-network.js';
|
|
9
9
|
export * from './adaptive-rag.js';
|
|
10
|
+
export { createContextFragmentationEngine } from './context-fragmentation.js';
|
|
11
|
+
export { createHybridSearchEngine, createBM25Index, bm25Score, normalizeBM25Scores, DEFAULT_HYBRID_CONFIG } from './hybrid-search.js';
|
|
@@ -0,0 +1,437 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Concept Graph - Structured Knowledge Representation
|
|
3
|
+
*
|
|
4
|
+
* Builds and maintains a graph of typed concepts extracted from
|
|
5
|
+
* memory entries. Each concept has structured metadata (frontmatter-
|
|
6
|
+
* style) and relationships to other concepts.
|
|
7
|
+
*
|
|
8
|
+
* Based on oh-my-ontology's approach: markdown frontmatter as graph,
|
|
9
|
+
* but adapted for AI agent memory systems.
|
|
10
|
+
*
|
|
11
|
+
* @module core/ontology
|
|
12
|
+
* @fileoverview Concept graph implementation
|
|
13
|
+
*/
|
|
14
|
+
import { randomUUID } from 'crypto';
|
|
15
|
+
import { createLogger } from '../../utils/logger.js';
|
|
16
|
+
const logger = createLogger('[ConceptGraph]');
|
|
17
|
+
// ============================================================
|
|
18
|
+
// Constants
|
|
19
|
+
// ============================================================
|
|
20
|
+
const VALID_DOMAINS = [
|
|
21
|
+
'auth', 'memory', 'cognition', 'collaboration', 'identity',
|
|
22
|
+
'evolution', 'consciousness', 'psychology', 'security', 'meta'
|
|
23
|
+
];
|
|
24
|
+
const VALID_KINDS = [
|
|
25
|
+
'entity', 'capability', 'principle', 'pattern', 'process', 'rule'
|
|
26
|
+
];
|
|
27
|
+
const DOMAIN_ALIASES = {
|
|
28
|
+
'mcp': 'collaboration',
|
|
29
|
+
'multi-agent': 'collaboration',
|
|
30
|
+
'agent': 'collaboration',
|
|
31
|
+
'memory': 'memory',
|
|
32
|
+
' episodic': 'memory',
|
|
33
|
+
'semantic': 'memory',
|
|
34
|
+
'consciousness': 'consciousness',
|
|
35
|
+
'identity': 'identity',
|
|
36
|
+
'self': 'identity',
|
|
37
|
+
'evolution': 'evolution',
|
|
38
|
+
'learn': 'evolution',
|
|
39
|
+
'psychology': 'psychology',
|
|
40
|
+
'emotion': 'psychology',
|
|
41
|
+
'security': 'security',
|
|
42
|
+
'safety': 'security',
|
|
43
|
+
};
|
|
44
|
+
// ============================================================
|
|
45
|
+
// Utility Functions
|
|
46
|
+
// ============================================================
|
|
47
|
+
function normalizeDomain(domain) {
|
|
48
|
+
const lower = domain.toLowerCase().replace(/[_-]/g, '-');
|
|
49
|
+
return DOMAIN_ALIASES[lower] || lower;
|
|
50
|
+
}
|
|
51
|
+
function slugify(text) {
|
|
52
|
+
return text
|
|
53
|
+
.toLowerCase()
|
|
54
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
55
|
+
.replace(/^-|-$/g, '')
|
|
56
|
+
.slice(0, 64);
|
|
57
|
+
}
|
|
58
|
+
function extractKeywords(text) {
|
|
59
|
+
const stopWords = new Set([
|
|
60
|
+
'the', 'a', 'an', 'is', 'are', 'was', 'were', 'be', 'been', 'being',
|
|
61
|
+
'have', 'has', 'had', 'do', 'does', 'did', 'will', 'would', 'could',
|
|
62
|
+
'should', 'may', 'might', 'must', 'can', 'need', 'to', 'of', 'in',
|
|
63
|
+
'for', 'on', 'with', 'at', 'by', 'from', 'as', 'into', 'through',
|
|
64
|
+
'and', 'or', 'if', 'but', 'so', 'than', 'then', 'when', 'where',
|
|
65
|
+
'why', 'how', 'what', 'which', 'who', 'whom', 'this', 'that', 'these',
|
|
66
|
+
'those', 'it', 'its', 'they', 'their', 'them', 'we', 'our', 'us',
|
|
67
|
+
'你', '我', '他', '她', '它', '的', '是', '在', '了', '和', '与'
|
|
68
|
+
]);
|
|
69
|
+
const words = [];
|
|
70
|
+
const tokens = text.toLowerCase().split(/\s+/);
|
|
71
|
+
for (const token of tokens) {
|
|
72
|
+
const cleaned = token.replace(/[^a-zA-Z\u4e00-\u9fff]/g, '');
|
|
73
|
+
if (cleaned.length >= 3 && !stopWords.has(cleaned)) {
|
|
74
|
+
words.push(cleaned);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return [...new Set(words)].slice(0, 10);
|
|
78
|
+
}
|
|
79
|
+
const DEFAULT_OPTIONS = {
|
|
80
|
+
minConfidence: 0.5,
|
|
81
|
+
orphanMaxAge: 7 * 24 * 60 * 60 * 1000, // 7 days
|
|
82
|
+
autoLink: true,
|
|
83
|
+
};
|
|
84
|
+
export function createConceptGraph(options = {}) {
|
|
85
|
+
const opts = { ...DEFAULT_OPTIONS, ...options };
|
|
86
|
+
const concepts = new Map();
|
|
87
|
+
const conceptsBySlug = new Map(); // key: domain/slug
|
|
88
|
+
const relations = new Map();
|
|
89
|
+
const memoryToConcepts = new Map(); // memoryId -> conceptIds
|
|
90
|
+
function buildSlugKey(domain, slug) {
|
|
91
|
+
return `${domain}/${slug}`;
|
|
92
|
+
}
|
|
93
|
+
function createConcept(data) {
|
|
94
|
+
const now = Date.now();
|
|
95
|
+
const domain = normalizeDomain(data.domain);
|
|
96
|
+
const slug = data.slug || slugify(data.name);
|
|
97
|
+
// Check for duplicate slug in domain
|
|
98
|
+
const key = buildSlugKey(domain, slug);
|
|
99
|
+
if (conceptsBySlug.has(key)) {
|
|
100
|
+
const existing = conceptsBySlug.get(key);
|
|
101
|
+
// Merge sources if same slug
|
|
102
|
+
if (data.sourceMemories) {
|
|
103
|
+
existing.sourceMemories = [...new Set([...existing.sourceMemories, ...data.sourceMemories])];
|
|
104
|
+
existing.updatedAt = now;
|
|
105
|
+
}
|
|
106
|
+
logger.info(`Merged with existing concept: ${key}`);
|
|
107
|
+
return existing;
|
|
108
|
+
}
|
|
109
|
+
const concept = {
|
|
110
|
+
id: data.id || randomUUID(),
|
|
111
|
+
slug,
|
|
112
|
+
name: data.name,
|
|
113
|
+
domain,
|
|
114
|
+
kind: data.kind || 'entity',
|
|
115
|
+
description: data.description || '',
|
|
116
|
+
properties: data.properties || {},
|
|
117
|
+
dependsOn: data.dependsOn || [],
|
|
118
|
+
dependents: data.dependents || [],
|
|
119
|
+
sourceMemories: data.sourceMemories || [],
|
|
120
|
+
confidence: data.confidence || opts.minConfidence,
|
|
121
|
+
referenceCount: 0,
|
|
122
|
+
lastReferenced: now,
|
|
123
|
+
createdAt: now,
|
|
124
|
+
updatedAt: now,
|
|
125
|
+
};
|
|
126
|
+
concepts.set(concept.id, concept);
|
|
127
|
+
conceptsBySlug.set(key, concept);
|
|
128
|
+
logger.info(`Created concept: ${key} (${concept.kind})`);
|
|
129
|
+
return concept;
|
|
130
|
+
}
|
|
131
|
+
function getConcept(id) {
|
|
132
|
+
return concepts.get(id) || null;
|
|
133
|
+
}
|
|
134
|
+
function getConceptBySlug(slug, domain) {
|
|
135
|
+
return conceptsBySlug.get(buildSlugKey(normalizeDomain(domain), slug)) || null;
|
|
136
|
+
}
|
|
137
|
+
function updateConcept(id, updates) {
|
|
138
|
+
const concept = concepts.get(id);
|
|
139
|
+
if (!concept)
|
|
140
|
+
return null;
|
|
141
|
+
// Update slug if name/domain changed
|
|
142
|
+
if (updates.domain && updates.domain !== concept.domain) {
|
|
143
|
+
const oldKey = buildSlugKey(concept.domain, concept.slug);
|
|
144
|
+
conceptsBySlug.delete(oldKey);
|
|
145
|
+
concept.domain = normalizeDomain(updates.domain);
|
|
146
|
+
}
|
|
147
|
+
if (updates.name && updates.name !== concept.name) {
|
|
148
|
+
const oldKey = buildSlugKey(concept.domain, concept.slug);
|
|
149
|
+
conceptsBySlug.delete(oldKey);
|
|
150
|
+
concept.slug = slugify(updates.name);
|
|
151
|
+
}
|
|
152
|
+
Object.assign(concept, updates, { updatedAt: Date.now() });
|
|
153
|
+
const newKey = buildSlugKey(concept.domain, concept.slug);
|
|
154
|
+
conceptsBySlug.set(newKey, concept);
|
|
155
|
+
logger.info(`Updated concept: ${concept.id}`);
|
|
156
|
+
return concept;
|
|
157
|
+
}
|
|
158
|
+
function deleteConcept(id) {
|
|
159
|
+
const concept = concepts.get(id);
|
|
160
|
+
if (!concept)
|
|
161
|
+
return false;
|
|
162
|
+
// Remove from slug index
|
|
163
|
+
const key = buildSlugKey(concept.domain, concept.slug);
|
|
164
|
+
conceptsBySlug.delete(key);
|
|
165
|
+
// Remove relations involving this concept
|
|
166
|
+
for (const [relId, rel] of relations) {
|
|
167
|
+
if (rel.sourceId === id || rel.targetId === id) {
|
|
168
|
+
relations.delete(relId);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
// Remove from memory links
|
|
172
|
+
for (const [, conceptIds] of memoryToConcepts) {
|
|
173
|
+
conceptIds.delete(id);
|
|
174
|
+
}
|
|
175
|
+
// Update dependents of concepts this depended on
|
|
176
|
+
for (const depId of concept.dependsOn) {
|
|
177
|
+
const dep = concepts.get(depId);
|
|
178
|
+
if (dep) {
|
|
179
|
+
dep.dependents = dep.dependents.filter(d => d !== id);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
concepts.delete(id);
|
|
183
|
+
logger.info(`Deleted concept: ${id}`);
|
|
184
|
+
return true;
|
|
185
|
+
}
|
|
186
|
+
function createRelation(sourceId, targetId, relation, weight = 0.8) {
|
|
187
|
+
const source = concepts.get(sourceId);
|
|
188
|
+
const target = concepts.get(targetId);
|
|
189
|
+
if (!source || !target) {
|
|
190
|
+
logger.warn(`Cannot create relation: concept not found`);
|
|
191
|
+
return null;
|
|
192
|
+
}
|
|
193
|
+
// Check for duplicate
|
|
194
|
+
for (const rel of relations.values()) {
|
|
195
|
+
if (rel.sourceId === sourceId && rel.targetId === targetId && rel.relation === relation) {
|
|
196
|
+
return rel;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
const rel = {
|
|
200
|
+
id: randomUUID(),
|
|
201
|
+
sourceId,
|
|
202
|
+
targetId,
|
|
203
|
+
relation,
|
|
204
|
+
weight,
|
|
205
|
+
bidirectional: ['associates', 'part-of'].includes(relation),
|
|
206
|
+
createdAt: Date.now(),
|
|
207
|
+
};
|
|
208
|
+
relations.set(rel.id, rel);
|
|
209
|
+
// Update concept dependency lists
|
|
210
|
+
if (!source.dependsOn.includes(targetId) && (relation === 'depends-on' || relation === 'enables')) {
|
|
211
|
+
source.dependsOn.push(targetId);
|
|
212
|
+
}
|
|
213
|
+
if (!target.dependents.includes(sourceId)) {
|
|
214
|
+
target.dependents.push(sourceId);
|
|
215
|
+
}
|
|
216
|
+
logger.info(`Created relation: ${source.slug} --[${relation}]--> ${target.slug}`);
|
|
217
|
+
return rel;
|
|
218
|
+
}
|
|
219
|
+
function deleteRelation(relationId) {
|
|
220
|
+
const rel = relations.get(relationId);
|
|
221
|
+
if (!rel)
|
|
222
|
+
return false;
|
|
223
|
+
// Update concept dependency lists
|
|
224
|
+
const source = concepts.get(rel.sourceId);
|
|
225
|
+
const target = concepts.get(rel.targetId);
|
|
226
|
+
if (source) {
|
|
227
|
+
source.dependsOn = source.dependsOn.filter(id => id !== rel.targetId);
|
|
228
|
+
}
|
|
229
|
+
if (target) {
|
|
230
|
+
target.dependents = target.dependents.filter(id => id !== rel.sourceId);
|
|
231
|
+
}
|
|
232
|
+
relations.delete(relationId);
|
|
233
|
+
return true;
|
|
234
|
+
}
|
|
235
|
+
function getConceptRelations(conceptId) {
|
|
236
|
+
const result = [];
|
|
237
|
+
for (const rel of relations.values()) {
|
|
238
|
+
if (rel.sourceId === conceptId || rel.targetId === conceptId) {
|
|
239
|
+
result.push(rel);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
return result;
|
|
243
|
+
}
|
|
244
|
+
function search(query) {
|
|
245
|
+
const results = [];
|
|
246
|
+
const now = Date.now();
|
|
247
|
+
for (const concept of concepts.values()) {
|
|
248
|
+
let score = 0;
|
|
249
|
+
// Domain filter
|
|
250
|
+
if (query.domain) {
|
|
251
|
+
if (concept.domain !== normalizeDomain(query.domain))
|
|
252
|
+
continue;
|
|
253
|
+
score += 0.3;
|
|
254
|
+
}
|
|
255
|
+
// Kind filter
|
|
256
|
+
if (query.kind) {
|
|
257
|
+
if (concept.kind !== query.kind)
|
|
258
|
+
continue;
|
|
259
|
+
score += 0.2;
|
|
260
|
+
}
|
|
261
|
+
// Slug exact match
|
|
262
|
+
if (query.slug) {
|
|
263
|
+
if (concept.slug !== query.slug)
|
|
264
|
+
continue;
|
|
265
|
+
score += 0.4;
|
|
266
|
+
}
|
|
267
|
+
// Text search
|
|
268
|
+
if (query.search) {
|
|
269
|
+
const searchLower = query.search.toLowerCase();
|
|
270
|
+
const searchTerms = extractKeywords(query.search);
|
|
271
|
+
if (concept.name.toLowerCase().includes(searchLower)) {
|
|
272
|
+
score += 0.3;
|
|
273
|
+
}
|
|
274
|
+
if (concept.description.toLowerCase().includes(searchLower)) {
|
|
275
|
+
score += 0.2;
|
|
276
|
+
}
|
|
277
|
+
if (searchTerms.some(term => {
|
|
278
|
+
const descLower = concept.description.toLowerCase();
|
|
279
|
+
const propStr = JSON.stringify(concept.properties).toLowerCase();
|
|
280
|
+
return descLower.includes(term) || propStr.includes(term);
|
|
281
|
+
})) {
|
|
282
|
+
score += 0.15;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
// Confidence filter
|
|
286
|
+
if (query.minConfidence && concept.confidence < query.minConfidence) {
|
|
287
|
+
continue;
|
|
288
|
+
}
|
|
289
|
+
if (score > 0) {
|
|
290
|
+
results.push({ concept, score });
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
// Sort by score, then by reference count and recency
|
|
294
|
+
results.sort((a, b) => {
|
|
295
|
+
if (b.score !== a.score)
|
|
296
|
+
return b.score - a.score;
|
|
297
|
+
const aEngagement = a.concept.referenceCount * (1 / Math.max(1, now - a.concept.lastReferenced));
|
|
298
|
+
const bEngagement = b.concept.referenceCount * (1 / Math.max(1, now - b.concept.lastReferenced));
|
|
299
|
+
return bEngagement - aEngagement;
|
|
300
|
+
});
|
|
301
|
+
const limit = query.limit || 20;
|
|
302
|
+
return results.slice(0, limit).map(r => r.concept);
|
|
303
|
+
}
|
|
304
|
+
function infer(conceptId, depth = 2) {
|
|
305
|
+
const rootConcept = concepts.get(conceptId);
|
|
306
|
+
if (!rootConcept)
|
|
307
|
+
return [];
|
|
308
|
+
const results = [];
|
|
309
|
+
const visited = new Set([conceptId]);
|
|
310
|
+
function traverse(currentId, path, currentDepth, currentConcept) {
|
|
311
|
+
if (currentDepth >= depth)
|
|
312
|
+
return;
|
|
313
|
+
const relations = getConceptRelations(currentId);
|
|
314
|
+
for (const rel of relations) {
|
|
315
|
+
const neighborId = rel.sourceId === currentId ? rel.targetId : rel.sourceId;
|
|
316
|
+
if (visited.has(neighborId))
|
|
317
|
+
continue;
|
|
318
|
+
visited.add(neighborId);
|
|
319
|
+
const neighbor = concepts.get(neighborId);
|
|
320
|
+
if (!neighbor)
|
|
321
|
+
continue;
|
|
322
|
+
const newPath = [...path, neighbor.slug];
|
|
323
|
+
const reasoning = `Through ${rel.relation} relation from ${path[path.length - 1] || currentConcept.slug}`;
|
|
324
|
+
results.push({
|
|
325
|
+
path: newPath,
|
|
326
|
+
inferredConcept: neighbor,
|
|
327
|
+
confidence: currentConcept.confidence * rel.weight * Math.pow(0.9, currentDepth),
|
|
328
|
+
reasoning,
|
|
329
|
+
});
|
|
330
|
+
traverse(neighborId, newPath, currentDepth + 1, neighbor);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
traverse(conceptId, [rootConcept.slug], 0, rootConcept);
|
|
334
|
+
return results.sort((a, b) => b.confidence - a.confidence);
|
|
335
|
+
}
|
|
336
|
+
function referenceConcept(conceptId) {
|
|
337
|
+
const concept = concepts.get(conceptId);
|
|
338
|
+
if (concept) {
|
|
339
|
+
concept.referenceCount++;
|
|
340
|
+
concept.lastReferenced = Date.now();
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
function linkMemoryToConcepts(memoryId, conceptIds) {
|
|
344
|
+
memoryToConcepts.set(memoryId, new Set(conceptIds));
|
|
345
|
+
for (const cid of conceptIds) {
|
|
346
|
+
const concept = concepts.get(cid);
|
|
347
|
+
if (concept && !concept.sourceMemories.includes(memoryId)) {
|
|
348
|
+
concept.sourceMemories.push(memoryId);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
function getOrphanConcepts() {
|
|
353
|
+
const now = Date.now();
|
|
354
|
+
const orphans = [];
|
|
355
|
+
for (const concept of concepts.values()) {
|
|
356
|
+
const relations = getConceptRelations(concept.id);
|
|
357
|
+
const hasConnections = relations.length > 0 || concept.dependsOn.length > 0 || concept.dependents.length > 0;
|
|
358
|
+
const isStale = now - concept.lastReferenced > opts.orphanMaxAge;
|
|
359
|
+
if (!hasConnections || isStale) {
|
|
360
|
+
orphans.push(concept);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
return orphans;
|
|
364
|
+
}
|
|
365
|
+
function getStats() {
|
|
366
|
+
const now = Date.now();
|
|
367
|
+
const domains = new Set();
|
|
368
|
+
let totalConnections = 0;
|
|
369
|
+
let mostConnected = [];
|
|
370
|
+
const orphanConcepts = getOrphanConcepts();
|
|
371
|
+
const recentlyReferenced = [];
|
|
372
|
+
for (const concept of concepts.values()) {
|
|
373
|
+
domains.add(concept.domain);
|
|
374
|
+
const rels = getConceptRelations(concept.id);
|
|
375
|
+
concept.referenceCount = rels.length + concept.dependsOn.length + concept.dependents.length;
|
|
376
|
+
totalConnections += rels.length;
|
|
377
|
+
if (now - concept.lastReferenced < 24 * 60 * 60 * 1000) {
|
|
378
|
+
recentlyReferenced.push(concept);
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
mostConnected = [...concepts.values()]
|
|
382
|
+
.sort((a, b) => b.referenceCount - a.referenceCount)
|
|
383
|
+
.slice(0, 5);
|
|
384
|
+
return {
|
|
385
|
+
totalConcepts: concepts.size,
|
|
386
|
+
totalRelations: relations.size,
|
|
387
|
+
domains: [...domains].sort(),
|
|
388
|
+
avgConnections: concepts.size > 0 ? totalConnections / concepts.size : 0,
|
|
389
|
+
mostConnected,
|
|
390
|
+
orphanConcepts,
|
|
391
|
+
recentlyReferenced: recentlyReferenced.sort((a, b) => b.lastReferenced - a.lastReferenced),
|
|
392
|
+
};
|
|
393
|
+
}
|
|
394
|
+
function exportData() {
|
|
395
|
+
return {
|
|
396
|
+
concepts: [...concepts.values()],
|
|
397
|
+
relations: [...relations.values()],
|
|
398
|
+
};
|
|
399
|
+
}
|
|
400
|
+
function importData(data) {
|
|
401
|
+
clear();
|
|
402
|
+
for (const concept of data.concepts) {
|
|
403
|
+
concepts.set(concept.id, concept);
|
|
404
|
+
conceptsBySlug.set(buildSlugKey(concept.domain, concept.slug), concept);
|
|
405
|
+
}
|
|
406
|
+
for (const rel of data.relations) {
|
|
407
|
+
relations.set(rel.id, rel);
|
|
408
|
+
}
|
|
409
|
+
logger.info(`Imported ${data.concepts.length} concepts, ${data.relations.length} relations`);
|
|
410
|
+
}
|
|
411
|
+
function clear() {
|
|
412
|
+
concepts.clear();
|
|
413
|
+
conceptsBySlug.clear();
|
|
414
|
+
relations.clear();
|
|
415
|
+
memoryToConcepts.clear();
|
|
416
|
+
logger.info('Cleared concept graph');
|
|
417
|
+
}
|
|
418
|
+
return {
|
|
419
|
+
createConcept,
|
|
420
|
+
getConcept,
|
|
421
|
+
getConceptBySlug,
|
|
422
|
+
updateConcept,
|
|
423
|
+
deleteConcept,
|
|
424
|
+
createRelation,
|
|
425
|
+
deleteRelation,
|
|
426
|
+
getConceptRelations,
|
|
427
|
+
search,
|
|
428
|
+
infer,
|
|
429
|
+
referenceConcept,
|
|
430
|
+
linkMemoryToConcepts,
|
|
431
|
+
getOrphanConcepts,
|
|
432
|
+
getStats,
|
|
433
|
+
exportData,
|
|
434
|
+
importData,
|
|
435
|
+
clear,
|
|
436
|
+
};
|
|
437
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Concept Ontology System
|
|
3
|
+
*
|
|
4
|
+
* Structured knowledge representation using typed concepts
|
|
5
|
+
* and relationships, inspired by oh-my-ontology's frontmatter
|
|
6
|
+
* graph approach. Enables AI-native concept reasoning.
|
|
7
|
+
*
|
|
8
|
+
* Key mechanisms:
|
|
9
|
+
* - Concept nodes with typed properties
|
|
10
|
+
* - Relationship edges with semantic meaning
|
|
11
|
+
* - Cross-reference resolution
|
|
12
|
+
* - Concept hierarchy (is-a, part-of)
|
|
13
|
+
* - Inference through relation traversal
|
|
14
|
+
*
|
|
15
|
+
* @module core/ontology
|
|
16
|
+
* @fileoverview Concept ontology for structured knowledge
|
|
17
|
+
*/
|
|
18
|
+
export * from './concept-graph.js';
|
package/dist/index.js
CHANGED
|
@@ -4,5 +4,8 @@ export { VERSION } from './version.js';
|
|
|
4
4
|
export * from './core/psychology/index.js';
|
|
5
5
|
export * from './core/cognition/index.js';
|
|
6
6
|
export { createAdaptiveRAGMemory, formatRetrievalResult } from './core/memory/adaptive-rag.js';
|
|
7
|
+
export { createContextFragmentationEngine } from './core/memory/context-fragmentation.js';
|
|
8
|
+
export { createIdentityContinuityVerifier } from './core/identity/identity-continuity.js';
|
|
9
|
+
export { createMCPProtocol } from './core/collaboration/mcp-protocol.js';
|
|
7
10
|
export { createTruthTeller, formatTruthStatement } from './core/truth-teller.js';
|
|
8
11
|
export { createActiveInferenceEngine, formatFreeEnergyMetrics, formatBeliefState } from './core/cognition/active-inference.js';
|
package/dist/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const VERSION = '2.2.
|
|
1
|
+
export const VERSION = '2.2.5';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mark-improving-agent",
|
|
3
|
-
"version": "2.2.
|
|
3
|
+
"version": "2.2.5",
|
|
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",
|