mark-improving-agent 2.3.4 → 2.3.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
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
2.3.
|
|
1
|
+
2.3.5
|
|
@@ -160,7 +160,7 @@ export function addToBinaryIndex(index, vectors, ids, metadata) {
|
|
|
160
160
|
return {
|
|
161
161
|
vectors: newVectors,
|
|
162
162
|
indices: [...index.indices, ...ids.map((_, i) => index.indices.length + i)],
|
|
163
|
-
metadata: [...index.metadata, ...metadata || ids.map(() => ({ id
|
|
163
|
+
metadata: [...index.metadata, ...metadata || ids.map((id, i) => ({ id, importance: 0.5, timestamp: Date.now() }))],
|
|
164
164
|
config: index.config,
|
|
165
165
|
};
|
|
166
166
|
}
|
|
@@ -0,0 +1,420 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Budget-Aware Memory Retrieval System
|
|
3
|
+
*
|
|
4
|
+
* A deterministic, token-budget-conscious memory retrieval system that:
|
|
5
|
+
* - Respects max token budgets during retrieval
|
|
6
|
+
* - Provides sub-millisecond query latency
|
|
7
|
+
* - Ensures identical queries return identical results
|
|
8
|
+
* - Supports semantic caching for similar queries
|
|
9
|
+
*
|
|
10
|
+
* Based on: Doorman11991/budget-aware-mcp (https://github.com/Doorman11991/budget-aware-mcp)
|
|
11
|
+
*
|
|
12
|
+
* Key concepts:
|
|
13
|
+
* - Hop-based graph walk: expand outward from anchor symbols hop by hop
|
|
14
|
+
* - Token budget: retrieval stops when budget is exhausted
|
|
15
|
+
* - Deterministic ordering: alphabetical within each hop level
|
|
16
|
+
* - Semantic cache: similar queries hit cache instantly
|
|
17
|
+
*
|
|
18
|
+
* @module core/memory
|
|
19
|
+
* @fileoverview Budget-aware memory retrieval with token control
|
|
20
|
+
*/
|
|
21
|
+
import { createLogger } from '../../utils/logger.js';
|
|
22
|
+
import { cosineSimilarity } from './embedder.js';
|
|
23
|
+
const logger = createLogger('[BudgetRetrieval]');
|
|
24
|
+
/**
|
|
25
|
+
* Trigram-based similarity for semantic caching
|
|
26
|
+
*/
|
|
27
|
+
function extractTrigrams(text) {
|
|
28
|
+
const normalized = text.toLowerCase().replace(/[^a-z0-9\s]/g, ' ');
|
|
29
|
+
const words = normalized.split(/\s+/).filter(w => w.length > 0);
|
|
30
|
+
const trigrams = [];
|
|
31
|
+
for (const word of words) {
|
|
32
|
+
if (word.length >= 3) {
|
|
33
|
+
for (let i = 0; i <= word.length - 3; i++) {
|
|
34
|
+
trigrams.push(word.slice(i, i + 3));
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return trigrams;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Jaccard similarity between two trigram sets
|
|
42
|
+
*/
|
|
43
|
+
function trigramSimilarity(a, b) {
|
|
44
|
+
if (a.length === 0 || b.length === 0)
|
|
45
|
+
return 0;
|
|
46
|
+
const setA = new Set(a);
|
|
47
|
+
const setB = new Set(b);
|
|
48
|
+
const intersection = new Set([...setA].filter(x => setB.has(x)));
|
|
49
|
+
const union = new Set([...setA, ...setB]);
|
|
50
|
+
return intersection.size / union.size;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Estimate tokens for a memory entry (rough approximation)
|
|
54
|
+
*/
|
|
55
|
+
function estimateTokens(entry) {
|
|
56
|
+
// Rough estimate: ~4 characters per token for typical English text
|
|
57
|
+
// Plus overhead for metadata
|
|
58
|
+
const contentTokens = Math.ceil(entry.content.length / 4);
|
|
59
|
+
const metadataTokens = 20; // tags, id, timestamps, etc.
|
|
60
|
+
return contentTokens + metadataTokens;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Create a deterministic hash from query
|
|
64
|
+
*/
|
|
65
|
+
function hashQuery(query) {
|
|
66
|
+
const parts = [
|
|
67
|
+
query.query.toLowerCase().trim(),
|
|
68
|
+
query.anchorId || '',
|
|
69
|
+
query.tier || '',
|
|
70
|
+
(query.maxTokens || 0).toString(),
|
|
71
|
+
(query.tags || []).sort().join(','),
|
|
72
|
+
(query.importanceThreshold || 0).toString()
|
|
73
|
+
];
|
|
74
|
+
// Simple hash function for determinism
|
|
75
|
+
const str = parts.join('|');
|
|
76
|
+
let hash = 0;
|
|
77
|
+
for (let i = 0; i < str.length; i++) {
|
|
78
|
+
const char = str.charCodeAt(i);
|
|
79
|
+
hash = ((hash << 5) - hash) + char;
|
|
80
|
+
hash = hash & hash; // Convert to 32-bit integer
|
|
81
|
+
}
|
|
82
|
+
return Math.abs(hash).toString(36);
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Alphabetical sort for deterministic ordering within hop levels
|
|
86
|
+
*/
|
|
87
|
+
function deterministicSort(entries) {
|
|
88
|
+
return entries.sort((a, b) => {
|
|
89
|
+
// First by hop level
|
|
90
|
+
if (a.hopLevel !== b.hopLevel) {
|
|
91
|
+
return a.hopLevel - b.hopLevel;
|
|
92
|
+
}
|
|
93
|
+
// Then alphabetically by content
|
|
94
|
+
return a.entry.content.localeCompare(b.entry.content);
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Default configuration
|
|
99
|
+
*/
|
|
100
|
+
export const DEFAULT_BUDGET_CONFIG = {
|
|
101
|
+
maxTokens: 4000,
|
|
102
|
+
tokensPerEntry: 200,
|
|
103
|
+
maxHops: 3,
|
|
104
|
+
enableCache: true,
|
|
105
|
+
cacheTTLMs: 5 * 60 * 1000, // 5 minutes
|
|
106
|
+
cacheSimilarityThreshold: 0.7,
|
|
107
|
+
enableImportanceFilter: true,
|
|
108
|
+
importanceThreshold: 0.3
|
|
109
|
+
};
|
|
110
|
+
/**
|
|
111
|
+
* Create a budget-aware retrieval engine
|
|
112
|
+
*/
|
|
113
|
+
export function createBudgetRetrievalEngine(config = {}, embedder) {
|
|
114
|
+
const fullConfig = { ...DEFAULT_BUDGET_CONFIG, ...config };
|
|
115
|
+
logger.info('Creating BudgetRetrievalEngine', { ...fullConfig });
|
|
116
|
+
// Memory storage
|
|
117
|
+
const entries = new Map();
|
|
118
|
+
const entriesByTier = new Map();
|
|
119
|
+
const entriesByTag = new Map();
|
|
120
|
+
// Embedding index (if embedder provided)
|
|
121
|
+
const entryEmbeddings = new Map();
|
|
122
|
+
// Semantic cache
|
|
123
|
+
const cache = new Map();
|
|
124
|
+
let cacheStats = { hits: 0, misses: 0, hitRate: 0, avgSimilarity: 0 };
|
|
125
|
+
/**
|
|
126
|
+
* Check semantic cache for similar query
|
|
127
|
+
*/
|
|
128
|
+
function checkCache(query) {
|
|
129
|
+
if (!fullConfig.enableCache)
|
|
130
|
+
return null;
|
|
131
|
+
const queryTrigrams = extractTrigrams(query.query);
|
|
132
|
+
const queryHash = hashQuery(query);
|
|
133
|
+
// Check exact match first
|
|
134
|
+
const exactEntry = cache.get(queryHash);
|
|
135
|
+
if (exactEntry) {
|
|
136
|
+
const age = Date.now() - exactEntry.timestamp;
|
|
137
|
+
if (age < fullConfig.cacheTTLMs) {
|
|
138
|
+
return exactEntry;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
// Check similar queries
|
|
142
|
+
let bestMatch = null;
|
|
143
|
+
let bestSimilarity = 0;
|
|
144
|
+
for (const entry of cache.values()) {
|
|
145
|
+
const age = Date.now() - entry.timestamp;
|
|
146
|
+
if (age >= fullConfig.cacheTTLMs)
|
|
147
|
+
continue;
|
|
148
|
+
const similarity = trigramSimilarity(queryTrigrams, entry.trigrams);
|
|
149
|
+
if (similarity >= fullConfig.cacheSimilarityThreshold && similarity > bestSimilarity) {
|
|
150
|
+
bestSimilarity = similarity;
|
|
151
|
+
bestMatch = entry;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
return bestMatch;
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Store result in cache
|
|
158
|
+
*/
|
|
159
|
+
function storeCache(query, result) {
|
|
160
|
+
if (!fullConfig.enableCache)
|
|
161
|
+
return;
|
|
162
|
+
const queryHash = hashQuery(query);
|
|
163
|
+
const trigrams = extractTrigrams(query.query);
|
|
164
|
+
cache.set(queryHash, {
|
|
165
|
+
queryHash,
|
|
166
|
+
queryText: query.query,
|
|
167
|
+
result,
|
|
168
|
+
timestamp: Date.now(),
|
|
169
|
+
trigrams
|
|
170
|
+
});
|
|
171
|
+
// Cleanup old entries
|
|
172
|
+
const now = Date.now();
|
|
173
|
+
for (const [key, entry] of cache.entries()) {
|
|
174
|
+
if (now - entry.timestamp > fullConfig.cacheTTLMs) {
|
|
175
|
+
cache.delete(key);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Calculate relevance score for an entry
|
|
181
|
+
*/
|
|
182
|
+
function calculateRelevance(entry, query, anchorEntry) {
|
|
183
|
+
let relevance = 0;
|
|
184
|
+
// Base relevance from importance
|
|
185
|
+
relevance += entry.importance * 0.3;
|
|
186
|
+
// Text similarity if query provided
|
|
187
|
+
if (query.query && embedder) {
|
|
188
|
+
const queryEmb = embedder.embed(query.query);
|
|
189
|
+
const entryEmb = entryEmbeddings.get(entry.id);
|
|
190
|
+
if (queryEmb && entryEmb) {
|
|
191
|
+
const sim = cosineSimilarity(queryEmb, entryEmb);
|
|
192
|
+
relevance += sim * 0.4;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
// Content match
|
|
196
|
+
const queryLower = query.query.toLowerCase();
|
|
197
|
+
const contentLower = entry.content.toLowerCase();
|
|
198
|
+
if (contentLower.includes(queryLower)) {
|
|
199
|
+
relevance += 0.3;
|
|
200
|
+
}
|
|
201
|
+
// Tag match
|
|
202
|
+
if (query.tags && query.tags.length > 0) {
|
|
203
|
+
const entryTags = new Set(entry.tags.map(t => t.toLowerCase()));
|
|
204
|
+
const matchCount = query.tags.filter(t => entryTags.has(t.toLowerCase())).length;
|
|
205
|
+
relevance += (matchCount / query.tags.length) * 0.2;
|
|
206
|
+
}
|
|
207
|
+
// Hop-based relevance from anchor
|
|
208
|
+
if (anchorEntry) {
|
|
209
|
+
// Entries referenced by anchor = hop 1
|
|
210
|
+
const anchor = anchorEntry;
|
|
211
|
+
const ent = entry;
|
|
212
|
+
if (anchor.references?.includes(ent.id)) {
|
|
213
|
+
return relevance + 0.5 - 0.1; // hop 1 bonus
|
|
214
|
+
}
|
|
215
|
+
// Entries referencing anchor = hop 1
|
|
216
|
+
if (ent.references?.includes(anchor.id)) {
|
|
217
|
+
return relevance + 0.5 - 0.1;
|
|
218
|
+
}
|
|
219
|
+
// Same session = hop 2
|
|
220
|
+
if (anchor.sessionId && anchor.sessionId === ent.sessionId) {
|
|
221
|
+
return relevance + 0.4 - 0.2;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
return Math.min(1, relevance);
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Perform budget-aware retrieval
|
|
228
|
+
*/
|
|
229
|
+
async function retrieve(query) {
|
|
230
|
+
const startMs = Date.now();
|
|
231
|
+
const maxTokens = query.maxTokens || fullConfig.maxTokens;
|
|
232
|
+
logger.debug('Retrieving with budget', { query: query.query, maxTokens });
|
|
233
|
+
// Check cache first
|
|
234
|
+
const cached = checkCache(query);
|
|
235
|
+
if (cached) {
|
|
236
|
+
cacheStats.hits++;
|
|
237
|
+
cacheStats.avgSimilarity = (cacheStats.avgSimilarity * (cacheStats.hits - 1) + (cached.result.cacheSimilarity || 1)) / cacheStats.hits;
|
|
238
|
+
cacheStats.hitRate = cacheStats.hits / (cacheStats.hits + cacheStats.misses);
|
|
239
|
+
logger.debug('Cache hit', { similarity: cached.result.cacheSimilarity });
|
|
240
|
+
return {
|
|
241
|
+
...cached.result,
|
|
242
|
+
cacheHit: true,
|
|
243
|
+
processingMs: Date.now() - startMs
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
cacheStats.misses++;
|
|
247
|
+
cacheStats.hitRate = cacheStats.hits / (cacheStats.hits + cacheStats.misses);
|
|
248
|
+
// Get anchor entry if specified
|
|
249
|
+
const anchorEntry = query.anchorId ? entries.get(query.anchorId) : undefined;
|
|
250
|
+
// Collect candidate entries
|
|
251
|
+
let candidateIds;
|
|
252
|
+
if (query.tier) {
|
|
253
|
+
const tierSet = entriesByTier.get(query.tier);
|
|
254
|
+
candidateIds = tierSet ? Array.from(tierSet) : [];
|
|
255
|
+
}
|
|
256
|
+
else {
|
|
257
|
+
candidateIds = Array.from(entries.keys());
|
|
258
|
+
}
|
|
259
|
+
// Filter by tags if specified
|
|
260
|
+
if (query.tags && query.tags.length > 0) {
|
|
261
|
+
candidateIds = candidateIds.filter(id => {
|
|
262
|
+
const entry = entries.get(id);
|
|
263
|
+
if (!entry)
|
|
264
|
+
return false;
|
|
265
|
+
const entryTags = new Set(entry.tags.map(t => t.toLowerCase()));
|
|
266
|
+
return query.tags.some(t => entryTags.has(t.toLowerCase()));
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
// Score and filter candidates
|
|
270
|
+
const scoredCandidates = [];
|
|
271
|
+
for (const id of candidateIds) {
|
|
272
|
+
const entry = entries.get(id);
|
|
273
|
+
if (!entry)
|
|
274
|
+
continue;
|
|
275
|
+
// Importance filter
|
|
276
|
+
if (fullConfig.enableImportanceFilter && entry.importance < fullConfig.importanceThreshold) {
|
|
277
|
+
continue;
|
|
278
|
+
}
|
|
279
|
+
if (query.importanceThreshold && entry.importance < query.importanceThreshold) {
|
|
280
|
+
continue;
|
|
281
|
+
}
|
|
282
|
+
const relevance = calculateRelevance(entry, query, anchorEntry);
|
|
283
|
+
const tokenEstimate = estimateTokens(entry);
|
|
284
|
+
// Determine hop level
|
|
285
|
+
let hopLevel = 0;
|
|
286
|
+
if (anchorEntry) {
|
|
287
|
+
if (id === anchorEntry.id) {
|
|
288
|
+
hopLevel = 0;
|
|
289
|
+
}
|
|
290
|
+
else if ((anchorEntry.references?.includes(id)) || (entry.references?.includes(anchorEntry.id))) {
|
|
291
|
+
hopLevel = 1;
|
|
292
|
+
}
|
|
293
|
+
else {
|
|
294
|
+
hopLevel = 2;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
scoredCandidates.push({
|
|
298
|
+
entry,
|
|
299
|
+
relevance,
|
|
300
|
+
hopLevel,
|
|
301
|
+
tokenEstimate,
|
|
302
|
+
cumulativeTokens: 0 // Will be calculated after sorting
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
// Sort deterministically
|
|
306
|
+
const sorted = deterministicSort(scoredCandidates);
|
|
307
|
+
// Build budget-constrained result
|
|
308
|
+
const budget = {
|
|
309
|
+
maxTokens,
|
|
310
|
+
usedTokens: 0,
|
|
311
|
+
remainingTokens: maxTokens,
|
|
312
|
+
entriesSelected: 0
|
|
313
|
+
};
|
|
314
|
+
const selected = [];
|
|
315
|
+
for (const candidate of sorted) {
|
|
316
|
+
if (budget.remainingTokens < candidate.tokenEstimate) {
|
|
317
|
+
continue; // Can't afford this entry
|
|
318
|
+
}
|
|
319
|
+
if (candidate.hopLevel > fullConfig.maxHops) {
|
|
320
|
+
continue; // Exceeded max hops
|
|
321
|
+
}
|
|
322
|
+
candidate.cumulativeTokens = budget.usedTokens + candidate.tokenEstimate;
|
|
323
|
+
budget.usedTokens = candidate.cumulativeTokens;
|
|
324
|
+
budget.remainingTokens = maxTokens - budget.usedTokens;
|
|
325
|
+
budget.entriesSelected++;
|
|
326
|
+
selected.push(candidate);
|
|
327
|
+
}
|
|
328
|
+
const result = {
|
|
329
|
+
entries: selected,
|
|
330
|
+
tokensUsed: budget.usedTokens,
|
|
331
|
+
budgetRemaining: budget.remainingTokens,
|
|
332
|
+
cacheHit: false,
|
|
333
|
+
queryHash: hashQuery(query),
|
|
334
|
+
processingMs: Date.now() - startMs,
|
|
335
|
+
totalAvailable: candidateIds.length
|
|
336
|
+
};
|
|
337
|
+
// Store in cache
|
|
338
|
+
storeCache(query, result);
|
|
339
|
+
logger.debug('Retrieval complete', {
|
|
340
|
+
tokensUsed: result.tokensUsed,
|
|
341
|
+
entriesSelected: result.entries.length,
|
|
342
|
+
processingMs: result.processingMs,
|
|
343
|
+
cacheHit: result.cacheHit
|
|
344
|
+
});
|
|
345
|
+
return result;
|
|
346
|
+
}
|
|
347
|
+
/**
|
|
348
|
+
* Index a memory entry
|
|
349
|
+
*/
|
|
350
|
+
function index(entry) {
|
|
351
|
+
entries.set(entry.id, entry);
|
|
352
|
+
// Index by tier
|
|
353
|
+
if (!entriesByTier.has(entry.tier)) {
|
|
354
|
+
entriesByTier.set(entry.tier, new Set());
|
|
355
|
+
}
|
|
356
|
+
entriesByTier.get(entry.tier).add(entry.id);
|
|
357
|
+
// Index by tags
|
|
358
|
+
for (const tag of entry.tags) {
|
|
359
|
+
if (!entriesByTag.has(tag)) {
|
|
360
|
+
entriesByTag.set(tag, new Set());
|
|
361
|
+
}
|
|
362
|
+
entriesByTag.get(tag).add(entry.id);
|
|
363
|
+
}
|
|
364
|
+
// Embed for semantic search
|
|
365
|
+
if (embedder) {
|
|
366
|
+
entryEmbeddings.set(entry.id, embedder.embed(entry.content));
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
/**
|
|
370
|
+
* Remove entry from index
|
|
371
|
+
*/
|
|
372
|
+
function remove(id) {
|
|
373
|
+
const entry = entries.get(id);
|
|
374
|
+
if (!entry)
|
|
375
|
+
return;
|
|
376
|
+
entries.delete(id);
|
|
377
|
+
// Remove from tier index
|
|
378
|
+
entriesByTier.get(entry.tier)?.delete(id);
|
|
379
|
+
// Remove from tag indices
|
|
380
|
+
for (const tag of entry.tags) {
|
|
381
|
+
entriesByTag.get(tag)?.delete(id);
|
|
382
|
+
}
|
|
383
|
+
// Remove embedding
|
|
384
|
+
entryEmbeddings.delete(id);
|
|
385
|
+
}
|
|
386
|
+
/**
|
|
387
|
+
* Clear all entries
|
|
388
|
+
*/
|
|
389
|
+
function clear() {
|
|
390
|
+
entries.clear();
|
|
391
|
+
entriesByTier.clear();
|
|
392
|
+
entriesByTag.clear();
|
|
393
|
+
entryEmbeddings.clear();
|
|
394
|
+
cache.clear();
|
|
395
|
+
logger.info('Budget retrieval engine cleared');
|
|
396
|
+
}
|
|
397
|
+
/**
|
|
398
|
+
* Get cache statistics
|
|
399
|
+
*/
|
|
400
|
+
function getCacheStats() {
|
|
401
|
+
return { ...cacheStats };
|
|
402
|
+
}
|
|
403
|
+
/**
|
|
404
|
+
* Update configuration
|
|
405
|
+
*/
|
|
406
|
+
function updateConfig(config) {
|
|
407
|
+
Object.assign(fullConfig, config);
|
|
408
|
+
logger.debug('Config updated', config);
|
|
409
|
+
}
|
|
410
|
+
return {
|
|
411
|
+
retrieve,
|
|
412
|
+
index,
|
|
413
|
+
remove,
|
|
414
|
+
clear,
|
|
415
|
+
getCacheStats,
|
|
416
|
+
updateConfig
|
|
417
|
+
};
|
|
418
|
+
}
|
|
419
|
+
// Export utilities for external use
|
|
420
|
+
export { extractTrigrams, trigramSimilarity, estimateTokens, hashQuery };
|
|
@@ -12,3 +12,4 @@ export { createHybridSearchEngine, createBM25Index, bm25Score, normalizeBM25Scor
|
|
|
12
12
|
export { createPatternRecognizer } from './pattern-recognizer.js';
|
|
13
13
|
export { createMemoryObserver } from './observer.js';
|
|
14
14
|
export { createBinaryVectorCompressor, quantizeToBinary, hammingDistance, hammingToSimilarity, createBinaryIndex, addToBinaryIndex, searchBinaryIndex, getCompressionStats, serializeBinaryIndex, deserializeBinaryIndex, DEFAULT_BINARY_CONFIG, EMBEDDING_DIMENSIONS } from './binary-vector.js';
|
|
15
|
+
export { createBudgetRetrievalEngine, DEFAULT_BUDGET_CONFIG, extractTrigrams, trigramSimilarity, estimateTokens, hashQuery } from './budget-retrieval.js';
|
package/dist/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const VERSION = '2.3.
|
|
1
|
+
export const VERSION = '2.3.5';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mark-improving-agent",
|
|
3
|
-
"version": "2.3.
|
|
3
|
+
"version": "2.3.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",
|