@elizaos/plugin-experience 2.0.0-alpha.1
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.
|
@@ -0,0 +1,1274 @@
|
|
|
1
|
+
// index.ts
|
|
2
|
+
import { logger as logger5 } from "@elizaos/core";
|
|
3
|
+
|
|
4
|
+
// actions/record-experience.ts
|
|
5
|
+
import {
|
|
6
|
+
createUniqueUuid,
|
|
7
|
+
logger
|
|
8
|
+
} from "@elizaos/core";
|
|
9
|
+
|
|
10
|
+
// generated/specs/specs.ts
|
|
11
|
+
var coreActionsSpec = {
|
|
12
|
+
version: "1.0.0",
|
|
13
|
+
actions: [
|
|
14
|
+
{
|
|
15
|
+
name: "RECORD_EXPERIENCE",
|
|
16
|
+
description: "Record a learning or experience for future reference. Use this when the user explicitly asks you to remember something or when you've learned something important.",
|
|
17
|
+
similes: ["REMEMBER", "LEARN", "STORE_EXPERIENCE", "SAVE_EXPERIENCE", "RECORD_LEARNING"],
|
|
18
|
+
parameters: [],
|
|
19
|
+
examples: [
|
|
20
|
+
[
|
|
21
|
+
{
|
|
22
|
+
name: "{{name1}}",
|
|
23
|
+
content: {
|
|
24
|
+
text: "Remember that installing dependencies is required for Python scripts"
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
name: "{{name2}}",
|
|
29
|
+
content: {
|
|
30
|
+
text: "I'll record that experience. Learning: Need to install dependencies before running Python scripts.",
|
|
31
|
+
actions: ["RECORD_EXPERIENCE"]
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
],
|
|
35
|
+
[
|
|
36
|
+
{
|
|
37
|
+
name: "{{name1}}",
|
|
38
|
+
content: {
|
|
39
|
+
text: "Remember that users prefer shorter responses"
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
name: "{{name2}}",
|
|
44
|
+
content: {
|
|
45
|
+
text: "I'll remember that preference.",
|
|
46
|
+
actions: ["RECORD_EXPERIENCE"]
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
],
|
|
50
|
+
[
|
|
51
|
+
{
|
|
52
|
+
name: "{{name1}}",
|
|
53
|
+
content: {
|
|
54
|
+
text: "What's 2+2?"
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
name: "{{name2}}",
|
|
59
|
+
content: {
|
|
60
|
+
text: "2+2 equals 4."
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
],
|
|
64
|
+
[
|
|
65
|
+
{
|
|
66
|
+
name: "{{name1}}",
|
|
67
|
+
content: {
|
|
68
|
+
text: "Can you help me with math?"
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
name: "{{name2}}",
|
|
73
|
+
content: {
|
|
74
|
+
text: "Of course! What math problem do you need help with?"
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
]
|
|
78
|
+
]
|
|
79
|
+
}
|
|
80
|
+
]
|
|
81
|
+
};
|
|
82
|
+
var allActionsSpec = {
|
|
83
|
+
version: "1.0.0",
|
|
84
|
+
actions: [
|
|
85
|
+
{
|
|
86
|
+
name: "RECORD_EXPERIENCE",
|
|
87
|
+
description: "Record a learning or experience for future reference. Use this when the user explicitly asks you to remember something or when you've learned something important.",
|
|
88
|
+
similes: ["REMEMBER", "LEARN", "STORE_EXPERIENCE", "SAVE_EXPERIENCE", "RECORD_LEARNING"],
|
|
89
|
+
parameters: [],
|
|
90
|
+
examples: [
|
|
91
|
+
[
|
|
92
|
+
{
|
|
93
|
+
name: "{{name1}}",
|
|
94
|
+
content: {
|
|
95
|
+
text: "Remember that installing dependencies is required for Python scripts"
|
|
96
|
+
}
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
name: "{{name2}}",
|
|
100
|
+
content: {
|
|
101
|
+
text: "I'll record that experience. Learning: Need to install dependencies before running Python scripts.",
|
|
102
|
+
actions: ["RECORD_EXPERIENCE"]
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
],
|
|
106
|
+
[
|
|
107
|
+
{
|
|
108
|
+
name: "{{name1}}",
|
|
109
|
+
content: {
|
|
110
|
+
text: "Remember that users prefer shorter responses"
|
|
111
|
+
}
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
name: "{{name2}}",
|
|
115
|
+
content: {
|
|
116
|
+
text: "I'll remember that preference.",
|
|
117
|
+
actions: ["RECORD_EXPERIENCE"]
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
],
|
|
121
|
+
[
|
|
122
|
+
{
|
|
123
|
+
name: "{{name1}}",
|
|
124
|
+
content: {
|
|
125
|
+
text: "What's 2+2?"
|
|
126
|
+
}
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
name: "{{name2}}",
|
|
130
|
+
content: {
|
|
131
|
+
text: "2+2 equals 4."
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
],
|
|
135
|
+
[
|
|
136
|
+
{
|
|
137
|
+
name: "{{name1}}",
|
|
138
|
+
content: {
|
|
139
|
+
text: "Can you help me with math?"
|
|
140
|
+
}
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
name: "{{name2}}",
|
|
144
|
+
content: {
|
|
145
|
+
text: "Of course! What math problem do you need help with?"
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
]
|
|
149
|
+
]
|
|
150
|
+
}
|
|
151
|
+
]
|
|
152
|
+
};
|
|
153
|
+
var coreProvidersSpec = {
|
|
154
|
+
version: "1.0.0",
|
|
155
|
+
providers: [
|
|
156
|
+
{
|
|
157
|
+
name: "experienceProvider",
|
|
158
|
+
description: "Provides relevant past experiences and learnings for the current context",
|
|
159
|
+
dynamic: true
|
|
160
|
+
}
|
|
161
|
+
]
|
|
162
|
+
};
|
|
163
|
+
var allProvidersSpec = {
|
|
164
|
+
version: "1.0.0",
|
|
165
|
+
providers: [
|
|
166
|
+
{
|
|
167
|
+
name: "experienceProvider",
|
|
168
|
+
description: "Provides relevant past experiences and learnings for the current context",
|
|
169
|
+
dynamic: true
|
|
170
|
+
}
|
|
171
|
+
]
|
|
172
|
+
};
|
|
173
|
+
var coreEvaluatorsSpec = {
|
|
174
|
+
version: "1.0.0",
|
|
175
|
+
evaluators: []
|
|
176
|
+
};
|
|
177
|
+
var allEvaluatorsSpec = {
|
|
178
|
+
version: "1.0.0",
|
|
179
|
+
evaluators: []
|
|
180
|
+
};
|
|
181
|
+
var coreActionDocs = coreActionsSpec.actions;
|
|
182
|
+
var allActionDocs = allActionsSpec.actions;
|
|
183
|
+
var coreProviderDocs = coreProvidersSpec.providers;
|
|
184
|
+
var allProviderDocs = allProvidersSpec.providers;
|
|
185
|
+
var coreEvaluatorDocs = coreEvaluatorsSpec.evaluators;
|
|
186
|
+
var allEvaluatorDocs = allEvaluatorsSpec.evaluators;
|
|
187
|
+
|
|
188
|
+
// generated/specs/spec-helpers.ts
|
|
189
|
+
var coreActionMap = new Map(coreActionDocs.map((doc) => [doc.name, doc]));
|
|
190
|
+
var allActionMap = new Map(allActionDocs.map((doc) => [doc.name, doc]));
|
|
191
|
+
var coreProviderMap = new Map(coreProviderDocs.map((doc) => [doc.name, doc]));
|
|
192
|
+
var allProviderMap = new Map(allProviderDocs.map((doc) => [doc.name, doc]));
|
|
193
|
+
var coreEvaluatorMap = new Map(coreEvaluatorDocs.map((doc) => [doc.name, doc]));
|
|
194
|
+
var allEvaluatorMap = new Map(allEvaluatorDocs.map((doc) => [doc.name, doc]));
|
|
195
|
+
function getActionSpec(name) {
|
|
196
|
+
return coreActionMap.get(name) ?? allActionMap.get(name);
|
|
197
|
+
}
|
|
198
|
+
function requireActionSpec(name) {
|
|
199
|
+
const spec = getActionSpec(name);
|
|
200
|
+
if (!spec) {
|
|
201
|
+
throw new Error(`Action spec not found: ${name}`);
|
|
202
|
+
}
|
|
203
|
+
return spec;
|
|
204
|
+
}
|
|
205
|
+
function getProviderSpec(name) {
|
|
206
|
+
return coreProviderMap.get(name) ?? allProviderMap.get(name);
|
|
207
|
+
}
|
|
208
|
+
function requireProviderSpec(name) {
|
|
209
|
+
const spec = getProviderSpec(name);
|
|
210
|
+
if (!spec) {
|
|
211
|
+
throw new Error(`Provider spec not found: ${name}`);
|
|
212
|
+
}
|
|
213
|
+
return spec;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// actions/record-experience.ts
|
|
217
|
+
var spec = requireActionSpec("RECORD_EXPERIENCE");
|
|
218
|
+
var recordExperienceAction = {
|
|
219
|
+
name: spec.name,
|
|
220
|
+
similes: spec.similes ? [...spec.similes] : [],
|
|
221
|
+
description: spec.description,
|
|
222
|
+
examples: spec.examples ?? [],
|
|
223
|
+
async validate(_runtime, message) {
|
|
224
|
+
const text = message.content.text?.toLowerCase();
|
|
225
|
+
return text?.includes("remember") || text?.includes("record") || false;
|
|
226
|
+
},
|
|
227
|
+
async handler(runtime, message, state, _options, _callback) {
|
|
228
|
+
logger.info("Recording experience for message:", message.id);
|
|
229
|
+
const experienceMemory = {
|
|
230
|
+
id: createUniqueUuid(runtime, `experience-${message.id}`),
|
|
231
|
+
entityId: message.entityId,
|
|
232
|
+
agentId: runtime.agentId,
|
|
233
|
+
roomId: message.roomId,
|
|
234
|
+
content: {
|
|
235
|
+
text: message.content.text,
|
|
236
|
+
source: message.content.source,
|
|
237
|
+
type: "experience",
|
|
238
|
+
context: state?.text
|
|
239
|
+
},
|
|
240
|
+
createdAt: Date.now()
|
|
241
|
+
};
|
|
242
|
+
await runtime.createMemory(experienceMemory, "experiences", true);
|
|
243
|
+
logger.info("Experience recorded successfully");
|
|
244
|
+
return {
|
|
245
|
+
success: true,
|
|
246
|
+
text: "Experience recorded.",
|
|
247
|
+
data: {
|
|
248
|
+
experienceMemoryId: experienceMemory.id
|
|
249
|
+
}
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
// evaluators/experienceEvaluator.ts
|
|
255
|
+
import {
|
|
256
|
+
composePrompt,
|
|
257
|
+
logger as logger2,
|
|
258
|
+
ModelType
|
|
259
|
+
} from "@elizaos/core";
|
|
260
|
+
|
|
261
|
+
// generated/prompts/typescript/prompts.ts
|
|
262
|
+
var extractExperiencesTemplate = `# Task: Extract Novel Learning Experiences
|
|
263
|
+
|
|
264
|
+
Analyze this conversation for novel learning experiences that would be surprising or valuable to remember.
|
|
265
|
+
|
|
266
|
+
## Conversation context
|
|
267
|
+
{{conversation_context}}
|
|
268
|
+
|
|
269
|
+
## Existing similar experiences
|
|
270
|
+
{{existing_experiences}}
|
|
271
|
+
|
|
272
|
+
## Instructions
|
|
273
|
+
Extract ONLY experiences that are:
|
|
274
|
+
1. Genuinely novel (not in existing experiences)
|
|
275
|
+
2. Actionable learnings about how things work
|
|
276
|
+
3. Corrections of previous mistakes or assumptions
|
|
277
|
+
4. Discoveries of new capabilities or patterns
|
|
278
|
+
5. Surprising outcomes that contradict expectations
|
|
279
|
+
|
|
280
|
+
Focus on technical knowledge, patterns, and cause-effect relationships that transfer to other contexts.
|
|
281
|
+
Avoid personal details, user-specific information, or routine interactions.
|
|
282
|
+
|
|
283
|
+
Respond with JSON array of experiences (max 3):
|
|
284
|
+
[{
|
|
285
|
+
"type": "DISCOVERY|CORRECTION|SUCCESS|LEARNING",
|
|
286
|
+
"learning": "What was learned (generic, transferable)",
|
|
287
|
+
"context": "What situation triggered this (anonymized)",
|
|
288
|
+
"confidence": 0.0-1.0,
|
|
289
|
+
"reasoning": "Why this is novel and valuable"
|
|
290
|
+
}]
|
|
291
|
+
|
|
292
|
+
Return empty array [] if no novel experiences found.`;
|
|
293
|
+
var EXTRACT_EXPERIENCES_TEMPLATE = extractExperiencesTemplate;
|
|
294
|
+
|
|
295
|
+
// types.ts
|
|
296
|
+
var ExperienceServiceType = {
|
|
297
|
+
EXPERIENCE: "EXPERIENCE"
|
|
298
|
+
};
|
|
299
|
+
var ExperienceType;
|
|
300
|
+
((ExperienceType2) => {
|
|
301
|
+
ExperienceType2["SUCCESS"] = "success";
|
|
302
|
+
ExperienceType2["FAILURE"] = "failure";
|
|
303
|
+
ExperienceType2["DISCOVERY"] = "discovery";
|
|
304
|
+
ExperienceType2["CORRECTION"] = "correction";
|
|
305
|
+
ExperienceType2["LEARNING"] = "learning";
|
|
306
|
+
ExperienceType2["HYPOTHESIS"] = "hypothesis";
|
|
307
|
+
ExperienceType2["VALIDATION"] = "validation";
|
|
308
|
+
ExperienceType2["WARNING"] = "warning";
|
|
309
|
+
})(ExperienceType ||= {});
|
|
310
|
+
var OutcomeType;
|
|
311
|
+
((OutcomeType2) => {
|
|
312
|
+
OutcomeType2["POSITIVE"] = "positive";
|
|
313
|
+
OutcomeType2["NEGATIVE"] = "negative";
|
|
314
|
+
OutcomeType2["NEUTRAL"] = "neutral";
|
|
315
|
+
OutcomeType2["MIXED"] = "mixed";
|
|
316
|
+
})(OutcomeType ||= {});
|
|
317
|
+
|
|
318
|
+
// evaluators/experienceEvaluator.ts
|
|
319
|
+
var experienceEvaluator = {
|
|
320
|
+
name: "EXPERIENCE_EVALUATOR",
|
|
321
|
+
similes: ["experience recorder", "learning evaluator", "self-reflection"],
|
|
322
|
+
description: "Periodically analyzes conversation patterns to extract novel learning experiences",
|
|
323
|
+
alwaysRun: false,
|
|
324
|
+
examples: [
|
|
325
|
+
{
|
|
326
|
+
prompt: "The agent successfully executed a shell command after initially failing",
|
|
327
|
+
messages: [
|
|
328
|
+
{
|
|
329
|
+
name: "Autoliza",
|
|
330
|
+
content: {
|
|
331
|
+
text: "Let me try to run this Python script."
|
|
332
|
+
}
|
|
333
|
+
},
|
|
334
|
+
{
|
|
335
|
+
name: "Autoliza",
|
|
336
|
+
content: {
|
|
337
|
+
text: "Error: ModuleNotFoundError for pandas. I need to install it first."
|
|
338
|
+
}
|
|
339
|
+
},
|
|
340
|
+
{
|
|
341
|
+
name: "Autoliza",
|
|
342
|
+
content: {
|
|
343
|
+
text: "After installing pandas, the script ran successfully and produced the expected output."
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
],
|
|
347
|
+
outcome: "Record a CORRECTION experience about needing to install dependencies before running Python scripts"
|
|
348
|
+
},
|
|
349
|
+
{
|
|
350
|
+
prompt: "The agent discovered a new system capability",
|
|
351
|
+
messages: [
|
|
352
|
+
{
|
|
353
|
+
name: "Autoliza",
|
|
354
|
+
content: {
|
|
355
|
+
text: "I found that the system has jq installed, which is perfect for parsing JSON data."
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
],
|
|
359
|
+
outcome: "Record a DISCOVERY experience about the availability of jq for JSON processing"
|
|
360
|
+
}
|
|
361
|
+
],
|
|
362
|
+
async validate(runtime, message, _state) {
|
|
363
|
+
if (message.entityId !== runtime.agentId) {
|
|
364
|
+
return false;
|
|
365
|
+
}
|
|
366
|
+
const lastExtractionKey = "experience-extraction:last-message-count";
|
|
367
|
+
const currentCount = await runtime.getCache(lastExtractionKey) || "0";
|
|
368
|
+
const messageCount = Number.parseInt(currentCount, 10);
|
|
369
|
+
const newMessageCount = messageCount + 1;
|
|
370
|
+
await runtime.setCache(lastExtractionKey, newMessageCount.toString());
|
|
371
|
+
const shouldExtract = newMessageCount % 10 === 0;
|
|
372
|
+
if (shouldExtract) {
|
|
373
|
+
logger2.info(`[experienceEvaluator] Triggering experience extraction after ${newMessageCount} messages`);
|
|
374
|
+
}
|
|
375
|
+
return shouldExtract;
|
|
376
|
+
},
|
|
377
|
+
async handler(runtime, message, state, _options, _callback, _responses) {
|
|
378
|
+
const experienceService = runtime.getService("EXPERIENCE");
|
|
379
|
+
if (!experienceService) {
|
|
380
|
+
logger2.warn("[experienceEvaluator] Experience service not available");
|
|
381
|
+
return;
|
|
382
|
+
}
|
|
383
|
+
const recentMessages = await runtime.getMemories({
|
|
384
|
+
tableName: "messages",
|
|
385
|
+
roomId: message.roomId,
|
|
386
|
+
count: 10,
|
|
387
|
+
unique: false
|
|
388
|
+
});
|
|
389
|
+
if (recentMessages.length < 3) {
|
|
390
|
+
logger2.debug("[experienceEvaluator] Not enough messages for experience extraction");
|
|
391
|
+
return;
|
|
392
|
+
}
|
|
393
|
+
const conversationContext = recentMessages.map((m) => m.content.text).filter(Boolean).join(" ");
|
|
394
|
+
const existingExperiences = await experienceService.queryExperiences({
|
|
395
|
+
query: conversationContext,
|
|
396
|
+
limit: 10,
|
|
397
|
+
minConfidence: 0.7
|
|
398
|
+
});
|
|
399
|
+
const existingExperiencesText = existingExperiences.length > 0 ? existingExperiences.map((exp) => `- ${exp.learning}`).join(`
|
|
400
|
+
`) : "None";
|
|
401
|
+
const extractionPrompt = composePrompt({
|
|
402
|
+
state: {
|
|
403
|
+
conversation_context: conversationContext,
|
|
404
|
+
existing_experiences: existingExperiencesText
|
|
405
|
+
},
|
|
406
|
+
template: EXTRACT_EXPERIENCES_TEMPLATE
|
|
407
|
+
});
|
|
408
|
+
const response = await runtime.useModel(ModelType.TEXT_LARGE, {
|
|
409
|
+
prompt: extractionPrompt
|
|
410
|
+
});
|
|
411
|
+
const experiences = parseExtractedExperiences(response);
|
|
412
|
+
const threshold = getNumberSetting(runtime, "AUTO_RECORD_THRESHOLD", 0.6);
|
|
413
|
+
const experienceTypeMap = {
|
|
414
|
+
DISCOVERY: "discovery" /* DISCOVERY */,
|
|
415
|
+
CORRECTION: "correction" /* CORRECTION */,
|
|
416
|
+
SUCCESS: "success" /* SUCCESS */,
|
|
417
|
+
LEARNING: "learning" /* LEARNING */
|
|
418
|
+
};
|
|
419
|
+
for (const exp of experiences.slice(0, 3)) {
|
|
420
|
+
if (!exp.learning || typeof exp.confidence !== "number" || exp.confidence < threshold) {
|
|
421
|
+
continue;
|
|
422
|
+
}
|
|
423
|
+
const normalizedType = typeof exp.type === "string" ? exp.type.toUpperCase() : "";
|
|
424
|
+
const experienceType = experienceTypeMap[normalizedType] ?? "learning" /* LEARNING */;
|
|
425
|
+
const experienceTag = experienceType;
|
|
426
|
+
await experienceService.recordExperience({
|
|
427
|
+
type: experienceType,
|
|
428
|
+
outcome: experienceType === "correction" /* CORRECTION */ ? "positive" /* POSITIVE */ : "neutral" /* NEUTRAL */,
|
|
429
|
+
context: sanitizeContext(exp.context || "Conversation analysis"),
|
|
430
|
+
action: "pattern_recognition",
|
|
431
|
+
result: exp.learning,
|
|
432
|
+
learning: sanitizeContext(exp.learning),
|
|
433
|
+
domain: detectDomain(exp.learning),
|
|
434
|
+
tags: ["extracted", "novel", experienceTag],
|
|
435
|
+
confidence: Math.min(exp.confidence, 0.9),
|
|
436
|
+
importance: 0.8
|
|
437
|
+
});
|
|
438
|
+
logger2.info(`[experienceEvaluator] Recorded novel experience: ${exp.learning.substring(0, 100)}...`);
|
|
439
|
+
}
|
|
440
|
+
if (experiences.length > 0) {
|
|
441
|
+
logger2.info(`[experienceEvaluator] Extracted ${experiences.length} novel experiences from conversation`);
|
|
442
|
+
} else {
|
|
443
|
+
logger2.debug("[experienceEvaluator] No novel experiences found in recent conversation");
|
|
444
|
+
}
|
|
445
|
+
return {
|
|
446
|
+
success: true,
|
|
447
|
+
data: {
|
|
448
|
+
extractedCount: experiences.length
|
|
449
|
+
},
|
|
450
|
+
values: {
|
|
451
|
+
extractedCount: experiences.length.toString()
|
|
452
|
+
}
|
|
453
|
+
};
|
|
454
|
+
}
|
|
455
|
+
};
|
|
456
|
+
function parseExtractedExperiences(response) {
|
|
457
|
+
const jsonMatch = response.match(/\[[\s\S]*\]/);
|
|
458
|
+
if (!jsonMatch)
|
|
459
|
+
return [];
|
|
460
|
+
try {
|
|
461
|
+
const parsed = JSON.parse(jsonMatch[0]);
|
|
462
|
+
if (!Array.isArray(parsed))
|
|
463
|
+
return [];
|
|
464
|
+
return parsed.filter((item) => item && typeof item === "object");
|
|
465
|
+
} catch {
|
|
466
|
+
return [];
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
function getNumberSetting(runtime, key, fallback) {
|
|
470
|
+
const value = runtime.getSetting(key);
|
|
471
|
+
if (typeof value === "number")
|
|
472
|
+
return value;
|
|
473
|
+
if (typeof value === "string") {
|
|
474
|
+
const parsed = Number.parseFloat(value);
|
|
475
|
+
return Number.isFinite(parsed) ? parsed : fallback;
|
|
476
|
+
}
|
|
477
|
+
return fallback;
|
|
478
|
+
}
|
|
479
|
+
function sanitizeContext(text) {
|
|
480
|
+
if (!text)
|
|
481
|
+
return "Unknown context";
|
|
482
|
+
return text.replace(/\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/g, "[EMAIL]").replace(/\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b/g, "[IP]").replace(/\/Users\/[^/\s]+/g, "/Users/[USER]").replace(/\/home\/[^/\s]+/g, "/home/[USER]").replace(/\b[A-Z0-9]{20,}\b/g, "[TOKEN]").replace(/\b(user|person|someone|they)\s+(said|asked|told|mentioned)/gi, "when asked").substring(0, 200);
|
|
483
|
+
}
|
|
484
|
+
function detectDomain(text) {
|
|
485
|
+
const domains = {
|
|
486
|
+
shell: ["command", "terminal", "bash", "shell", "execute", "script", "cli"],
|
|
487
|
+
coding: [
|
|
488
|
+
"code",
|
|
489
|
+
"function",
|
|
490
|
+
"variable",
|
|
491
|
+
"syntax",
|
|
492
|
+
"programming",
|
|
493
|
+
"debug",
|
|
494
|
+
"typescript",
|
|
495
|
+
"javascript"
|
|
496
|
+
],
|
|
497
|
+
system: ["file", "directory", "process", "memory", "cpu", "system", "install", "package"],
|
|
498
|
+
network: ["http", "api", "request", "response", "url", "network", "fetch", "curl"],
|
|
499
|
+
data: ["json", "csv", "database", "query", "data", "sql", "table"],
|
|
500
|
+
ai: ["model", "llm", "embedding", "prompt", "token", "inference"]
|
|
501
|
+
};
|
|
502
|
+
const lowerText = text.toLowerCase();
|
|
503
|
+
for (const [domain, keywords] of Object.entries(domains)) {
|
|
504
|
+
if (keywords.some((keyword) => lowerText.includes(keyword))) {
|
|
505
|
+
return domain;
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
return "general";
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
// providers/experienceProvider.ts
|
|
512
|
+
import {
|
|
513
|
+
logger as logger3
|
|
514
|
+
} from "@elizaos/core";
|
|
515
|
+
var spec2 = requireProviderSpec("experienceProvider");
|
|
516
|
+
var experienceProvider = {
|
|
517
|
+
name: spec2.name,
|
|
518
|
+
description: "Provides relevant past experiences and learnings for the current context",
|
|
519
|
+
async get(runtime, message, _state) {
|
|
520
|
+
const experienceService = runtime.getService("EXPERIENCE");
|
|
521
|
+
if (!experienceService) {
|
|
522
|
+
return { text: "", data: {}, values: {} };
|
|
523
|
+
}
|
|
524
|
+
const messageText = message.content.text || "";
|
|
525
|
+
if (messageText.length < 10) {
|
|
526
|
+
return { text: "", data: {}, values: {} };
|
|
527
|
+
}
|
|
528
|
+
const relevantExperiences = await experienceService.queryExperiences({
|
|
529
|
+
query: messageText,
|
|
530
|
+
limit: 5,
|
|
531
|
+
minConfidence: 0.6,
|
|
532
|
+
minImportance: 0.5
|
|
533
|
+
});
|
|
534
|
+
if (relevantExperiences.length === 0) {
|
|
535
|
+
return { text: "", data: {}, values: {} };
|
|
536
|
+
}
|
|
537
|
+
const experienceText = relevantExperiences.map((exp, index) => {
|
|
538
|
+
return `Experience ${index + 1}: In ${exp.domain} context, when ${exp.context}, I learned: ${exp.learning}`;
|
|
539
|
+
}).join(`
|
|
540
|
+
`);
|
|
541
|
+
const contextText = `[RELEVANT EXPERIENCES]
|
|
542
|
+
${experienceText}
|
|
543
|
+
[/RELEVANT EXPERIENCES]`;
|
|
544
|
+
logger3.debug(`[experienceProvider] Injecting ${relevantExperiences.length} relevant experiences`);
|
|
545
|
+
return {
|
|
546
|
+
text: contextText,
|
|
547
|
+
data: {
|
|
548
|
+
experiences: relevantExperiences,
|
|
549
|
+
count: relevantExperiences.length
|
|
550
|
+
},
|
|
551
|
+
values: {
|
|
552
|
+
experienceCount: relevantExperiences.length.toString()
|
|
553
|
+
}
|
|
554
|
+
};
|
|
555
|
+
}
|
|
556
|
+
};
|
|
557
|
+
|
|
558
|
+
// service.ts
|
|
559
|
+
import {
|
|
560
|
+
logger as logger4,
|
|
561
|
+
ModelType as ModelType2,
|
|
562
|
+
Service
|
|
563
|
+
} from "@elizaos/core";
|
|
564
|
+
import { v4 as uuidv4 } from "uuid";
|
|
565
|
+
|
|
566
|
+
// utils/confidenceDecay.ts
|
|
567
|
+
var DEFAULT_DECAY_CONFIG = {
|
|
568
|
+
halfLife: 30 * 24 * 60 * 60 * 1000,
|
|
569
|
+
minConfidence: 0.1,
|
|
570
|
+
decayStartDelay: 7 * 24 * 60 * 60 * 1000
|
|
571
|
+
};
|
|
572
|
+
|
|
573
|
+
class ConfidenceDecayManager {
|
|
574
|
+
config;
|
|
575
|
+
constructor(config = {}) {
|
|
576
|
+
this.config = { ...DEFAULT_DECAY_CONFIG, ...config };
|
|
577
|
+
}
|
|
578
|
+
getDecayedConfidence(experience) {
|
|
579
|
+
const now = Date.now();
|
|
580
|
+
const age = now - experience.createdAt;
|
|
581
|
+
const specificConfig = this.getDomainSpecificDecay(experience);
|
|
582
|
+
if (age < specificConfig.decayStartDelay) {
|
|
583
|
+
return experience.confidence;
|
|
584
|
+
}
|
|
585
|
+
const decayTime = age - specificConfig.decayStartDelay;
|
|
586
|
+
const halfLives = decayTime / specificConfig.halfLife;
|
|
587
|
+
const decayFactor = 0.5 ** halfLives;
|
|
588
|
+
const decayedConfidence = experience.confidence * decayFactor;
|
|
589
|
+
return Math.max(specificConfig.minConfidence, decayedConfidence);
|
|
590
|
+
}
|
|
591
|
+
getExperiencesNeedingReinforcement(experiences, threshold = 0.3) {
|
|
592
|
+
return experiences.filter((exp) => {
|
|
593
|
+
const decayed = this.getDecayedConfidence(exp);
|
|
594
|
+
return decayed < threshold && decayed > this.config.minConfidence;
|
|
595
|
+
});
|
|
596
|
+
}
|
|
597
|
+
calculateReinforcementBoost(experience, validationStrength = 1) {
|
|
598
|
+
const currentConfidence = this.getDecayedConfidence(experience);
|
|
599
|
+
const boost = (1 - currentConfidence) * validationStrength * 0.5;
|
|
600
|
+
return Math.min(1, currentConfidence + boost);
|
|
601
|
+
}
|
|
602
|
+
getDomainSpecificDecay(experience) {
|
|
603
|
+
const config = { ...this.config };
|
|
604
|
+
if (experience.type === "discovery" /* DISCOVERY */ || experience.type === "learning" /* LEARNING */) {
|
|
605
|
+
config.halfLife *= 2;
|
|
606
|
+
}
|
|
607
|
+
if (experience.type === "warning" /* WARNING */ || experience.type === "correction" /* CORRECTION */) {
|
|
608
|
+
config.halfLife *= 1.5;
|
|
609
|
+
config.minConfidence = 0.2;
|
|
610
|
+
}
|
|
611
|
+
switch (experience.domain) {
|
|
612
|
+
case "security":
|
|
613
|
+
case "safety":
|
|
614
|
+
config.halfLife *= 3;
|
|
615
|
+
config.minConfidence = 0.3;
|
|
616
|
+
break;
|
|
617
|
+
case "performance":
|
|
618
|
+
config.halfLife *= 0.5;
|
|
619
|
+
break;
|
|
620
|
+
case "user_preference":
|
|
621
|
+
config.halfLife *= 0.7;
|
|
622
|
+
break;
|
|
623
|
+
}
|
|
624
|
+
return config;
|
|
625
|
+
}
|
|
626
|
+
getConfidenceTrend(experience, points = 10) {
|
|
627
|
+
const trend = [];
|
|
628
|
+
const now = Date.now();
|
|
629
|
+
const totalTime = now - experience.createdAt;
|
|
630
|
+
const interval = totalTime / (points - 1);
|
|
631
|
+
const specificConfig = this.getDomainSpecificDecay(experience);
|
|
632
|
+
for (let i = 0;i < points; i++) {
|
|
633
|
+
const timestamp = experience.createdAt + interval * i;
|
|
634
|
+
const age = timestamp - experience.createdAt;
|
|
635
|
+
let confidence;
|
|
636
|
+
if (age < specificConfig.decayStartDelay) {
|
|
637
|
+
confidence = experience.confidence;
|
|
638
|
+
} else {
|
|
639
|
+
const decayTime = age - specificConfig.decayStartDelay;
|
|
640
|
+
const halfLives = decayTime / specificConfig.halfLife;
|
|
641
|
+
const decayFactor = 0.5 ** halfLives;
|
|
642
|
+
confidence = Math.max(specificConfig.minConfidence, experience.confidence * decayFactor);
|
|
643
|
+
}
|
|
644
|
+
trend.push({ timestamp, confidence });
|
|
645
|
+
}
|
|
646
|
+
return trend;
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
// utils/experienceRelationships.ts
|
|
651
|
+
class ExperienceRelationshipManager {
|
|
652
|
+
relationships = new Map;
|
|
653
|
+
addRelationship(relationship) {
|
|
654
|
+
const { fromId } = relationship;
|
|
655
|
+
if (!this.relationships.has(fromId)) {
|
|
656
|
+
this.relationships.set(fromId, []);
|
|
657
|
+
}
|
|
658
|
+
this.relationships.get(fromId)?.push(relationship);
|
|
659
|
+
}
|
|
660
|
+
findRelationships(experienceId, type) {
|
|
661
|
+
const rels = this.relationships.get(experienceId) || [];
|
|
662
|
+
if (type) {
|
|
663
|
+
return rels.filter((r) => r.type === type);
|
|
664
|
+
}
|
|
665
|
+
return rels;
|
|
666
|
+
}
|
|
667
|
+
detectCausalChain(experiences) {
|
|
668
|
+
const chains = [];
|
|
669
|
+
const sorted = [...experiences].sort((a, b) => a.createdAt - b.createdAt);
|
|
670
|
+
for (let i = 0;i < sorted.length - 1; i++) {
|
|
671
|
+
const current = sorted[i];
|
|
672
|
+
if (!current) {
|
|
673
|
+
continue;
|
|
674
|
+
}
|
|
675
|
+
if (current.type === "hypothesis" /* HYPOTHESIS */) {
|
|
676
|
+
const chain = [current.id];
|
|
677
|
+
let j = i + 1;
|
|
678
|
+
while (j < sorted.length) {
|
|
679
|
+
const next = sorted[j];
|
|
680
|
+
if (!next) {
|
|
681
|
+
j++;
|
|
682
|
+
continue;
|
|
683
|
+
}
|
|
684
|
+
if (next.relatedExperiences?.includes(current.id) || this.isRelated(current, next)) {
|
|
685
|
+
chain.push(next.id);
|
|
686
|
+
if (next.type === "validation" /* VALIDATION */) {
|
|
687
|
+
chains.push({
|
|
688
|
+
rootExperience: current.id,
|
|
689
|
+
chain,
|
|
690
|
+
strength: next.confidence,
|
|
691
|
+
validated: next.outcome === "positive" /* POSITIVE */
|
|
692
|
+
});
|
|
693
|
+
break;
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
j++;
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
return chains;
|
|
701
|
+
}
|
|
702
|
+
isRelated(exp1, exp2) {
|
|
703
|
+
if (exp1.domain === exp2.domain) {
|
|
704
|
+
const timeDiff = Math.abs(exp2.createdAt - exp1.createdAt);
|
|
705
|
+
if (timeDiff < 5 * 60 * 1000) {
|
|
706
|
+
if (this.contentSimilarity(exp1, exp2) > 0.7) {
|
|
707
|
+
return true;
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
return false;
|
|
712
|
+
}
|
|
713
|
+
contentSimilarity(exp1, exp2) {
|
|
714
|
+
const words1 = new Set(exp1.learning.toLowerCase().split(/\s+/));
|
|
715
|
+
const words2 = new Set(exp2.learning.toLowerCase().split(/\s+/));
|
|
716
|
+
const intersection = new Set([...words1].filter((x) => words2.has(x)));
|
|
717
|
+
const union = new Set([...words1, ...words2]);
|
|
718
|
+
return intersection.size / union.size;
|
|
719
|
+
}
|
|
720
|
+
findContradictions(experience, allExperiences) {
|
|
721
|
+
const contradictions = [];
|
|
722
|
+
for (const other of allExperiences) {
|
|
723
|
+
if (other.id === experience.id)
|
|
724
|
+
continue;
|
|
725
|
+
if (other.action === experience.action && other.outcome !== experience.outcome && other.domain === experience.domain) {
|
|
726
|
+
contradictions.push(other);
|
|
727
|
+
}
|
|
728
|
+
const rels = this.findRelationships(experience.id, "contradicts");
|
|
729
|
+
if (rels.some((r) => r.toId === other.id)) {
|
|
730
|
+
contradictions.push(other);
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
return contradictions;
|
|
734
|
+
}
|
|
735
|
+
getExperienceImpact(experienceId, allExperiences) {
|
|
736
|
+
let impact = 0;
|
|
737
|
+
for (const exp of allExperiences) {
|
|
738
|
+
if (exp.relatedExperiences?.includes(experienceId)) {
|
|
739
|
+
impact += exp.importance;
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
const relationships = this.findRelationships(experienceId);
|
|
743
|
+
for (const rel of relationships) {
|
|
744
|
+
if (rel.type === "causes") {
|
|
745
|
+
impact += rel.strength;
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
return impact;
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
// service.ts
|
|
753
|
+
class ExperienceService extends Service {
|
|
754
|
+
static serviceType = ExperienceServiceType.EXPERIENCE;
|
|
755
|
+
capabilityDescription = "Manages agent experiences, learning from successes and failures to improve future decisions";
|
|
756
|
+
experiences = new Map;
|
|
757
|
+
experiencesByDomain = new Map;
|
|
758
|
+
experiencesByType = new Map;
|
|
759
|
+
maxExperiences = 1e4;
|
|
760
|
+
decayManager;
|
|
761
|
+
relationshipManager;
|
|
762
|
+
constructor(runtime) {
|
|
763
|
+
super(runtime);
|
|
764
|
+
this.decayManager = new ConfidenceDecayManager;
|
|
765
|
+
this.relationshipManager = new ExperienceRelationshipManager;
|
|
766
|
+
const max = getNumberSetting2(runtime, "MAX_EXPERIENCES", this.maxExperiences);
|
|
767
|
+
if (Number.isFinite(max) && max > 0) {
|
|
768
|
+
this.maxExperiences = Math.floor(max);
|
|
769
|
+
}
|
|
770
|
+
this.loadExperiences();
|
|
771
|
+
}
|
|
772
|
+
static async start(runtime) {
|
|
773
|
+
const service = new ExperienceService(runtime);
|
|
774
|
+
return service;
|
|
775
|
+
}
|
|
776
|
+
setMaxExperiences(maxExperiences) {
|
|
777
|
+
if (!Number.isFinite(maxExperiences) || maxExperiences <= 0)
|
|
778
|
+
return;
|
|
779
|
+
this.maxExperiences = Math.floor(maxExperiences);
|
|
780
|
+
}
|
|
781
|
+
async loadExperiences() {
|
|
782
|
+
const allMemories = await this.runtime.getMemories({
|
|
783
|
+
entityId: this.runtime.agentId,
|
|
784
|
+
count: this.maxExperiences,
|
|
785
|
+
tableName: "memories"
|
|
786
|
+
});
|
|
787
|
+
const memories = allMemories.filter((m) => m.content.type === "experience");
|
|
788
|
+
for (const memory of memories) {
|
|
789
|
+
const experienceData = memory.content.data;
|
|
790
|
+
if (experienceData?.id) {
|
|
791
|
+
const memoryCreatedAt = typeof memory.createdAt === "number" ? memory.createdAt : Date.now();
|
|
792
|
+
const toTimestamp = (value, fallback) => {
|
|
793
|
+
if (value === undefined)
|
|
794
|
+
return fallback;
|
|
795
|
+
if (typeof value === "number")
|
|
796
|
+
return value;
|
|
797
|
+
if (value instanceof Date)
|
|
798
|
+
return value.getTime();
|
|
799
|
+
return fallback;
|
|
800
|
+
};
|
|
801
|
+
const experience = {
|
|
802
|
+
id: experienceData.id,
|
|
803
|
+
agentId: this.runtime.agentId,
|
|
804
|
+
type: experienceData.type || "learning" /* LEARNING */,
|
|
805
|
+
outcome: experienceData.outcome || "neutral" /* NEUTRAL */,
|
|
806
|
+
context: experienceData.context || "",
|
|
807
|
+
action: experienceData.action || "",
|
|
808
|
+
result: experienceData.result || "",
|
|
809
|
+
learning: experienceData.learning || "",
|
|
810
|
+
domain: experienceData.domain || "general",
|
|
811
|
+
tags: experienceData.tags || [],
|
|
812
|
+
confidence: experienceData.confidence || 0.5,
|
|
813
|
+
importance: experienceData.importance || 0.5,
|
|
814
|
+
createdAt: toTimestamp(experienceData.createdAt, memoryCreatedAt),
|
|
815
|
+
updatedAt: toTimestamp(experienceData.updatedAt, memoryCreatedAt),
|
|
816
|
+
accessCount: experienceData.accessCount ?? 0,
|
|
817
|
+
lastAccessedAt: toTimestamp(experienceData.lastAccessedAt, memoryCreatedAt),
|
|
818
|
+
embedding: experienceData.embedding,
|
|
819
|
+
relatedExperiences: experienceData.relatedExperiences,
|
|
820
|
+
supersedes: experienceData.supersedes,
|
|
821
|
+
previousBelief: experienceData.previousBelief,
|
|
822
|
+
correctedBelief: experienceData.correctedBelief
|
|
823
|
+
};
|
|
824
|
+
this.experiences.set(experience.id, experience);
|
|
825
|
+
if (!this.experiencesByDomain.has(experience.domain)) {
|
|
826
|
+
this.experiencesByDomain.set(experience.domain, new Set);
|
|
827
|
+
}
|
|
828
|
+
this.experiencesByDomain.get(experience.domain)?.add(experience.id);
|
|
829
|
+
if (!this.experiencesByType.has(experience.type)) {
|
|
830
|
+
this.experiencesByType.set(experience.type, new Set);
|
|
831
|
+
}
|
|
832
|
+
this.experiencesByType.get(experience.type)?.add(experience.id);
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
logger4.info(`[ExperienceService] Loaded ${this.experiences.size} experiences from memory`);
|
|
836
|
+
}
|
|
837
|
+
async recordExperience(experienceData) {
|
|
838
|
+
const embeddingText = `${experienceData.context} ${experienceData.action} ${experienceData.result} ${experienceData.learning}`;
|
|
839
|
+
const embedding = await this.runtime.useModel(ModelType2.TEXT_EMBEDDING, {
|
|
840
|
+
text: embeddingText
|
|
841
|
+
});
|
|
842
|
+
const now = Date.now();
|
|
843
|
+
const experience = {
|
|
844
|
+
id: uuidv4(),
|
|
845
|
+
agentId: this.runtime.agentId,
|
|
846
|
+
type: experienceData.type || "learning" /* LEARNING */,
|
|
847
|
+
outcome: experienceData.outcome || "neutral" /* NEUTRAL */,
|
|
848
|
+
context: experienceData.context || "",
|
|
849
|
+
action: experienceData.action || "",
|
|
850
|
+
result: experienceData.result || "",
|
|
851
|
+
learning: experienceData.learning || "",
|
|
852
|
+
domain: experienceData.domain || "general",
|
|
853
|
+
tags: experienceData.tags || [],
|
|
854
|
+
confidence: experienceData.confidence || 0.5,
|
|
855
|
+
importance: experienceData.importance || 0.5,
|
|
856
|
+
createdAt: now,
|
|
857
|
+
updatedAt: now,
|
|
858
|
+
accessCount: 0,
|
|
859
|
+
lastAccessedAt: now,
|
|
860
|
+
embedding,
|
|
861
|
+
relatedExperiences: experienceData.relatedExperiences,
|
|
862
|
+
supersedes: experienceData.supersedes,
|
|
863
|
+
previousBelief: experienceData.previousBelief,
|
|
864
|
+
correctedBelief: experienceData.correctedBelief
|
|
865
|
+
};
|
|
866
|
+
this.experiences.set(experience.id, experience);
|
|
867
|
+
if (!this.experiencesByDomain.has(experience.domain)) {
|
|
868
|
+
this.experiencesByDomain.set(experience.domain, new Set);
|
|
869
|
+
}
|
|
870
|
+
this.experiencesByDomain.get(experience.domain)?.add(experience.id);
|
|
871
|
+
if (!this.experiencesByType.has(experience.type)) {
|
|
872
|
+
this.experiencesByType.set(experience.type, new Set);
|
|
873
|
+
}
|
|
874
|
+
this.experiencesByType.get(experience.type)?.add(experience.id);
|
|
875
|
+
await this.saveExperienceToMemory(experience);
|
|
876
|
+
const allExperiences = Array.from(this.experiences.values());
|
|
877
|
+
const contradictions = this.relationshipManager.findContradictions(experience, allExperiences);
|
|
878
|
+
for (const contradiction of contradictions) {
|
|
879
|
+
this.relationshipManager.addRelationship({
|
|
880
|
+
fromId: experience.id,
|
|
881
|
+
toId: contradiction.id,
|
|
882
|
+
type: "contradicts",
|
|
883
|
+
strength: 0.8
|
|
884
|
+
});
|
|
885
|
+
}
|
|
886
|
+
if (this.experiences.size > this.maxExperiences) {
|
|
887
|
+
await this.pruneOldExperiences();
|
|
888
|
+
}
|
|
889
|
+
logger4.info(`[ExperienceService] Recorded experience: ${experience.id} (${experience.type})`);
|
|
890
|
+
return experience;
|
|
891
|
+
}
|
|
892
|
+
async saveExperienceToMemory(experience) {
|
|
893
|
+
const memory = {
|
|
894
|
+
id: experience.id,
|
|
895
|
+
entityId: this.runtime.agentId,
|
|
896
|
+
agentId: this.runtime.agentId,
|
|
897
|
+
roomId: this.runtime.agentId,
|
|
898
|
+
content: {
|
|
899
|
+
text: `Experience: ${experience.learning}`,
|
|
900
|
+
type: "experience",
|
|
901
|
+
data: {
|
|
902
|
+
id: experience.id,
|
|
903
|
+
agentId: experience.agentId,
|
|
904
|
+
type: experience.type,
|
|
905
|
+
outcome: experience.outcome,
|
|
906
|
+
context: experience.context,
|
|
907
|
+
action: experience.action,
|
|
908
|
+
result: experience.result,
|
|
909
|
+
learning: experience.learning,
|
|
910
|
+
domain: experience.domain,
|
|
911
|
+
tags: experience.tags,
|
|
912
|
+
confidence: experience.confidence,
|
|
913
|
+
importance: experience.importance,
|
|
914
|
+
createdAt: experience.createdAt,
|
|
915
|
+
updatedAt: experience.updatedAt,
|
|
916
|
+
accessCount: experience.accessCount,
|
|
917
|
+
lastAccessedAt: experience.lastAccessedAt,
|
|
918
|
+
embedding: experience.embedding,
|
|
919
|
+
relatedExperiences: experience.relatedExperiences,
|
|
920
|
+
supersedes: experience.supersedes,
|
|
921
|
+
previousBelief: experience.previousBelief,
|
|
922
|
+
correctedBelief: experience.correctedBelief
|
|
923
|
+
}
|
|
924
|
+
},
|
|
925
|
+
createdAt: experience.createdAt
|
|
926
|
+
};
|
|
927
|
+
await this.runtime.createMemory(memory, "experiences", true);
|
|
928
|
+
}
|
|
929
|
+
async queryExperiences(query) {
|
|
930
|
+
let results = [];
|
|
931
|
+
if (query.query) {
|
|
932
|
+
const similarExperiences = await this.findSimilarExperiences(query.query, query.limit || 10);
|
|
933
|
+
let candidates = similarExperiences;
|
|
934
|
+
if (query.type) {
|
|
935
|
+
const types = Array.isArray(query.type) ? query.type : [query.type];
|
|
936
|
+
candidates = candidates.filter((exp) => types.includes(exp.type));
|
|
937
|
+
}
|
|
938
|
+
if (query.outcome) {
|
|
939
|
+
candidates = candidates.filter((exp) => exp.outcome === query.outcome);
|
|
940
|
+
}
|
|
941
|
+
if (query.domain) {
|
|
942
|
+
const domains = Array.isArray(query.domain) ? query.domain : [query.domain];
|
|
943
|
+
candidates = candidates.filter((exp) => domains.includes(exp.domain));
|
|
944
|
+
}
|
|
945
|
+
if (query.tags && query.tags.length > 0) {
|
|
946
|
+
candidates = candidates.filter((exp) => query.tags?.some((tag) => exp.tags.includes(tag)));
|
|
947
|
+
}
|
|
948
|
+
if (query.minConfidence !== undefined) {
|
|
949
|
+
const minConfidence = query.minConfidence;
|
|
950
|
+
candidates = candidates.filter((exp) => {
|
|
951
|
+
const decayedConfidence = this.decayManager.getDecayedConfidence(exp);
|
|
952
|
+
return decayedConfidence >= minConfidence;
|
|
953
|
+
});
|
|
954
|
+
}
|
|
955
|
+
if (query.minImportance !== undefined) {
|
|
956
|
+
const minImportance = query.minImportance;
|
|
957
|
+
candidates = candidates.filter((exp) => exp.importance >= minImportance);
|
|
958
|
+
}
|
|
959
|
+
if (query.timeRange) {
|
|
960
|
+
candidates = candidates.filter((exp) => {
|
|
961
|
+
if (query.timeRange?.start && exp.createdAt < query.timeRange?.start)
|
|
962
|
+
return false;
|
|
963
|
+
if (query.timeRange?.end && exp.createdAt > query.timeRange?.end)
|
|
964
|
+
return false;
|
|
965
|
+
return true;
|
|
966
|
+
});
|
|
967
|
+
}
|
|
968
|
+
results = candidates;
|
|
969
|
+
} else {
|
|
970
|
+
let candidates = Array.from(this.experiences.values());
|
|
971
|
+
if (query.type) {
|
|
972
|
+
const types = Array.isArray(query.type) ? query.type : [query.type];
|
|
973
|
+
candidates = candidates.filter((exp) => types.includes(exp.type));
|
|
974
|
+
}
|
|
975
|
+
if (query.outcome) {
|
|
976
|
+
candidates = candidates.filter((exp) => exp.outcome === query.outcome);
|
|
977
|
+
}
|
|
978
|
+
if (query.domain) {
|
|
979
|
+
const domains = Array.isArray(query.domain) ? query.domain : [query.domain];
|
|
980
|
+
candidates = candidates.filter((exp) => domains.includes(exp.domain));
|
|
981
|
+
}
|
|
982
|
+
if (query.tags && query.tags.length > 0) {
|
|
983
|
+
candidates = candidates.filter((exp) => query.tags?.some((tag) => exp.tags.includes(tag)));
|
|
984
|
+
}
|
|
985
|
+
if (query.minConfidence !== undefined) {
|
|
986
|
+
const minConfidence = query.minConfidence;
|
|
987
|
+
candidates = candidates.filter((exp) => {
|
|
988
|
+
const decayedConfidence = this.decayManager.getDecayedConfidence(exp);
|
|
989
|
+
return decayedConfidence >= minConfidence;
|
|
990
|
+
});
|
|
991
|
+
}
|
|
992
|
+
if (query.minImportance !== undefined) {
|
|
993
|
+
const minImportance = query.minImportance;
|
|
994
|
+
candidates = candidates.filter((exp) => exp.importance >= minImportance);
|
|
995
|
+
}
|
|
996
|
+
if (query.timeRange) {
|
|
997
|
+
candidates = candidates.filter((exp) => {
|
|
998
|
+
if (query.timeRange?.start && exp.createdAt < query.timeRange?.start)
|
|
999
|
+
return false;
|
|
1000
|
+
if (query.timeRange?.end && exp.createdAt > query.timeRange?.end)
|
|
1001
|
+
return false;
|
|
1002
|
+
return true;
|
|
1003
|
+
});
|
|
1004
|
+
}
|
|
1005
|
+
candidates.sort((a, b) => {
|
|
1006
|
+
const scoreA = this.decayManager.getDecayedConfidence(a) * a.importance;
|
|
1007
|
+
const scoreB = this.decayManager.getDecayedConfidence(b) * b.importance;
|
|
1008
|
+
return scoreB - scoreA;
|
|
1009
|
+
});
|
|
1010
|
+
results = candidates.slice(0, query.limit || 10);
|
|
1011
|
+
}
|
|
1012
|
+
if (query.includeRelated) {
|
|
1013
|
+
const relatedIds = new Set;
|
|
1014
|
+
for (const exp of results) {
|
|
1015
|
+
if (exp.relatedExperiences) {
|
|
1016
|
+
exp.relatedExperiences.forEach((id) => {
|
|
1017
|
+
relatedIds.add(id);
|
|
1018
|
+
});
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
1021
|
+
const related = Array.from(relatedIds).map((id) => this.experiences.get(id)).filter((exp) => exp !== undefined).filter((exp) => !results.some((r) => r.id === exp.id));
|
|
1022
|
+
results.push(...related);
|
|
1023
|
+
}
|
|
1024
|
+
for (const exp of results) {
|
|
1025
|
+
exp.accessCount++;
|
|
1026
|
+
exp.lastAccessedAt = Date.now();
|
|
1027
|
+
}
|
|
1028
|
+
return results;
|
|
1029
|
+
}
|
|
1030
|
+
async findSimilarExperiences(text, limit = 5) {
|
|
1031
|
+
if (!text || this.experiences.size === 0) {
|
|
1032
|
+
return [];
|
|
1033
|
+
}
|
|
1034
|
+
const queryEmbedding = await this.runtime.useModel(ModelType2.TEXT_EMBEDDING, {
|
|
1035
|
+
text
|
|
1036
|
+
});
|
|
1037
|
+
const similarities = [];
|
|
1038
|
+
for (const experience of this.experiences.values()) {
|
|
1039
|
+
if (experience.embedding) {
|
|
1040
|
+
const similarity = this.cosineSimilarity(queryEmbedding, experience.embedding);
|
|
1041
|
+
similarities.push({ experience, similarity });
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
similarities.sort((a, b) => b.similarity - a.similarity);
|
|
1045
|
+
const results = similarities.slice(0, limit).map((item) => item.experience);
|
|
1046
|
+
for (const exp of results) {
|
|
1047
|
+
exp.accessCount++;
|
|
1048
|
+
exp.lastAccessedAt = Date.now();
|
|
1049
|
+
}
|
|
1050
|
+
return results;
|
|
1051
|
+
}
|
|
1052
|
+
async analyzeExperiences(domain, type) {
|
|
1053
|
+
const experiences = await this.queryExperiences({
|
|
1054
|
+
domain: domain ? [domain] : undefined,
|
|
1055
|
+
type: type ? [type] : undefined,
|
|
1056
|
+
limit: 100
|
|
1057
|
+
});
|
|
1058
|
+
if (experiences.length === 0) {
|
|
1059
|
+
return {
|
|
1060
|
+
pattern: "No experiences found for analysis",
|
|
1061
|
+
frequency: 0,
|
|
1062
|
+
reliability: 0,
|
|
1063
|
+
alternatives: [],
|
|
1064
|
+
recommendations: []
|
|
1065
|
+
};
|
|
1066
|
+
}
|
|
1067
|
+
const learnings = experiences.map((exp) => exp.learning);
|
|
1068
|
+
const commonWords = this.findCommonPatterns(learnings);
|
|
1069
|
+
const avgConfidence = experiences.reduce((sum, exp) => sum + exp.confidence, 0) / experiences.length;
|
|
1070
|
+
const outcomeConsistency = this.calculateOutcomeConsistency(experiences);
|
|
1071
|
+
const reliability = (avgConfidence + outcomeConsistency) / 2;
|
|
1072
|
+
const alternatives = this.extractAlternatives(experiences);
|
|
1073
|
+
const recommendations = this.generateRecommendations(experiences, reliability);
|
|
1074
|
+
return {
|
|
1075
|
+
pattern: commonWords.length > 0 ? `Common patterns: ${commonWords.join(", ")}` : "No clear patterns detected",
|
|
1076
|
+
frequency: experiences.length,
|
|
1077
|
+
reliability,
|
|
1078
|
+
alternatives,
|
|
1079
|
+
recommendations
|
|
1080
|
+
};
|
|
1081
|
+
}
|
|
1082
|
+
cosineSimilarity(a, b) {
|
|
1083
|
+
if (a.length !== b.length)
|
|
1084
|
+
return 0;
|
|
1085
|
+
let dotProduct = 0;
|
|
1086
|
+
let normA = 0;
|
|
1087
|
+
let normB = 0;
|
|
1088
|
+
for (let i = 0;i < a.length; i++) {
|
|
1089
|
+
const valueA = a[i] ?? 0;
|
|
1090
|
+
const valueB = b[i] ?? 0;
|
|
1091
|
+
dotProduct += valueA * valueB;
|
|
1092
|
+
normA += valueA * valueA;
|
|
1093
|
+
normB += valueB * valueB;
|
|
1094
|
+
}
|
|
1095
|
+
if (normA === 0 || normB === 0)
|
|
1096
|
+
return 0;
|
|
1097
|
+
return dotProduct / (Math.sqrt(normA) * Math.sqrt(normB));
|
|
1098
|
+
}
|
|
1099
|
+
findCommonPatterns(texts) {
|
|
1100
|
+
const wordFreq = new Map;
|
|
1101
|
+
for (const text of texts) {
|
|
1102
|
+
const words = text.toLowerCase().split(/\s+/);
|
|
1103
|
+
for (const word of words) {
|
|
1104
|
+
if (word.length > 3) {
|
|
1105
|
+
wordFreq.set(word, (wordFreq.get(word) || 0) + 1);
|
|
1106
|
+
}
|
|
1107
|
+
}
|
|
1108
|
+
}
|
|
1109
|
+
const threshold = texts.length * 0.3;
|
|
1110
|
+
return Array.from(wordFreq.entries()).filter(([_, count]) => count >= threshold).sort((a, b) => b[1] - a[1]).slice(0, 5).map(([word]) => word);
|
|
1111
|
+
}
|
|
1112
|
+
calculateOutcomeConsistency(experiences) {
|
|
1113
|
+
if (experiences.length === 0)
|
|
1114
|
+
return 0;
|
|
1115
|
+
const outcomeCounts = new Map;
|
|
1116
|
+
for (const exp of experiences) {
|
|
1117
|
+
outcomeCounts.set(exp.outcome, (outcomeCounts.get(exp.outcome) || 0) + 1);
|
|
1118
|
+
}
|
|
1119
|
+
const maxCount = Math.max(...outcomeCounts.values());
|
|
1120
|
+
return maxCount / experiences.length;
|
|
1121
|
+
}
|
|
1122
|
+
extractAlternatives(experiences) {
|
|
1123
|
+
const alternatives = new Set;
|
|
1124
|
+
for (const exp of experiences) {
|
|
1125
|
+
if (exp.type === "correction" /* CORRECTION */ && exp.correctedBelief) {
|
|
1126
|
+
alternatives.add(exp.correctedBelief);
|
|
1127
|
+
}
|
|
1128
|
+
if (exp.outcome === "negative" /* NEGATIVE */ && exp.learning.includes("instead")) {
|
|
1129
|
+
const match = exp.learning.match(/instead\s+(.+?)(?:\.|$)/i);
|
|
1130
|
+
const alternative = match?.[1]?.trim();
|
|
1131
|
+
if (alternative) {
|
|
1132
|
+
alternatives.add(alternative);
|
|
1133
|
+
}
|
|
1134
|
+
}
|
|
1135
|
+
}
|
|
1136
|
+
return Array.from(alternatives).slice(0, 5);
|
|
1137
|
+
}
|
|
1138
|
+
generateRecommendations(experiences, reliability) {
|
|
1139
|
+
const recommendations = [];
|
|
1140
|
+
if (reliability > 0.8) {
|
|
1141
|
+
recommendations.push("Continue using successful approaches");
|
|
1142
|
+
recommendations.push("Document and share these reliable methods");
|
|
1143
|
+
} else if (reliability > 0.6) {
|
|
1144
|
+
recommendations.push("Continue using successful approaches with caution");
|
|
1145
|
+
recommendations.push("Monitor for potential issues");
|
|
1146
|
+
recommendations.push("Consider backup strategies");
|
|
1147
|
+
} else if (reliability > 0.4) {
|
|
1148
|
+
recommendations.push("Review and improve current approaches");
|
|
1149
|
+
recommendations.push("Investigate failure patterns");
|
|
1150
|
+
recommendations.push("Consider alternative methods");
|
|
1151
|
+
} else {
|
|
1152
|
+
recommendations.push("Significant changes needed to current approach");
|
|
1153
|
+
recommendations.push("Analyze failure causes thoroughly");
|
|
1154
|
+
recommendations.push("Seek alternative solutions");
|
|
1155
|
+
}
|
|
1156
|
+
const failureTypes = new Map;
|
|
1157
|
+
experiences.filter((e) => e.outcome === "negative" /* NEGATIVE */).forEach((e) => {
|
|
1158
|
+
const key = e.learning.toLowerCase();
|
|
1159
|
+
failureTypes.set(key, (failureTypes.get(key) || 0) + 1);
|
|
1160
|
+
});
|
|
1161
|
+
if (failureTypes.size > 0) {
|
|
1162
|
+
const mostCommonFailure = Array.from(failureTypes.entries()).sort((a, b) => b[1] - a[1])[0];
|
|
1163
|
+
if (mostCommonFailure && mostCommonFailure[1] > 1) {
|
|
1164
|
+
recommendations.push(`Address recurring issue: ${mostCommonFailure[0]}`);
|
|
1165
|
+
}
|
|
1166
|
+
}
|
|
1167
|
+
const domains = new Set(experiences.map((e) => e.domain));
|
|
1168
|
+
if (domains.has("shell")) {
|
|
1169
|
+
recommendations.push("Verify command syntax and permissions");
|
|
1170
|
+
}
|
|
1171
|
+
if (domains.has("coding")) {
|
|
1172
|
+
recommendations.push("Test thoroughly before deployment");
|
|
1173
|
+
}
|
|
1174
|
+
if (domains.has("network")) {
|
|
1175
|
+
recommendations.push("Implement retry logic and error handling");
|
|
1176
|
+
}
|
|
1177
|
+
return recommendations.slice(0, 5);
|
|
1178
|
+
}
|
|
1179
|
+
async pruneOldExperiences() {
|
|
1180
|
+
if (this.experiences.size <= this.maxExperiences) {
|
|
1181
|
+
return;
|
|
1182
|
+
}
|
|
1183
|
+
const experienceArray = Array.from(this.experiences.values());
|
|
1184
|
+
experienceArray.sort((a, b) => {
|
|
1185
|
+
if (a.importance !== b.importance) {
|
|
1186
|
+
return a.importance - b.importance;
|
|
1187
|
+
}
|
|
1188
|
+
if (a.accessCount !== b.accessCount) {
|
|
1189
|
+
return a.accessCount - b.accessCount;
|
|
1190
|
+
}
|
|
1191
|
+
return a.createdAt - b.createdAt;
|
|
1192
|
+
});
|
|
1193
|
+
const toRemove = experienceArray.slice(0, experienceArray.length - this.maxExperiences);
|
|
1194
|
+
let removedCount = 0;
|
|
1195
|
+
for (const experience of toRemove) {
|
|
1196
|
+
this.experiences.delete(experience.id);
|
|
1197
|
+
const domainSet = this.experiencesByDomain.get(experience.domain);
|
|
1198
|
+
if (domainSet) {
|
|
1199
|
+
domainSet.delete(experience.id);
|
|
1200
|
+
if (domainSet.size === 0) {
|
|
1201
|
+
this.experiencesByDomain.delete(experience.domain);
|
|
1202
|
+
}
|
|
1203
|
+
}
|
|
1204
|
+
const typeSet = this.experiencesByType.get(experience.type);
|
|
1205
|
+
if (typeSet) {
|
|
1206
|
+
typeSet.delete(experience.id);
|
|
1207
|
+
if (typeSet.size === 0) {
|
|
1208
|
+
this.experiencesByType.delete(experience.type);
|
|
1209
|
+
}
|
|
1210
|
+
}
|
|
1211
|
+
removedCount++;
|
|
1212
|
+
}
|
|
1213
|
+
logger4.info(`[ExperienceService] Pruned ${removedCount} old experiences`);
|
|
1214
|
+
}
|
|
1215
|
+
async stop() {
|
|
1216
|
+
logger4.info("[ExperienceService] Stopping...");
|
|
1217
|
+
const experiencesToSave = Array.from(this.experiences.values());
|
|
1218
|
+
let savedCount = 0;
|
|
1219
|
+
for (const experience of experiencesToSave) {
|
|
1220
|
+
await this.saveExperienceToMemory(experience);
|
|
1221
|
+
savedCount++;
|
|
1222
|
+
}
|
|
1223
|
+
logger4.info(`[ExperienceService] Saved ${savedCount} experiences`);
|
|
1224
|
+
}
|
|
1225
|
+
}
|
|
1226
|
+
function getNumberSetting2(runtime, key, fallback) {
|
|
1227
|
+
const value = runtime.getSetting(key);
|
|
1228
|
+
if (typeof value === "number")
|
|
1229
|
+
return value;
|
|
1230
|
+
if (typeof value === "string") {
|
|
1231
|
+
const parsed = Number.parseFloat(value);
|
|
1232
|
+
return Number.isFinite(parsed) ? parsed : fallback;
|
|
1233
|
+
}
|
|
1234
|
+
return fallback;
|
|
1235
|
+
}
|
|
1236
|
+
|
|
1237
|
+
// index.ts
|
|
1238
|
+
var experiencePlugin = {
|
|
1239
|
+
name: "experience",
|
|
1240
|
+
description: "Self-learning experience system that records and recalls transferable agent learnings",
|
|
1241
|
+
actions: [recordExperienceAction],
|
|
1242
|
+
services: [ExperienceService],
|
|
1243
|
+
providers: [experienceProvider],
|
|
1244
|
+
evaluators: [experienceEvaluator],
|
|
1245
|
+
async init(config, runtime) {
|
|
1246
|
+
logger5.info("[ExperiencePlugin] Initializing experience learning system");
|
|
1247
|
+
const maxExperiences = parseOptionalNumber(config.MAX_EXPERIENCES, 1e4);
|
|
1248
|
+
const autoRecordThreshold = parseOptionalNumber(config.AUTO_RECORD_THRESHOLD, 0.7);
|
|
1249
|
+
runtime.setSetting("MAX_EXPERIENCES", maxExperiences.toString());
|
|
1250
|
+
runtime.setSetting("AUTO_RECORD_THRESHOLD", autoRecordThreshold.toString());
|
|
1251
|
+
const experienceService = runtime.getService("EXPERIENCE");
|
|
1252
|
+
experienceService?.setMaxExperiences(maxExperiences);
|
|
1253
|
+
logger5.info(`[ExperiencePlugin] Configuration:
|
|
1254
|
+
- MAX_EXPERIENCES: ${maxExperiences}
|
|
1255
|
+
- AUTO_RECORD_THRESHOLD: ${autoRecordThreshold}`);
|
|
1256
|
+
}
|
|
1257
|
+
};
|
|
1258
|
+
var typescript_default = experiencePlugin;
|
|
1259
|
+
function parseOptionalNumber(value, fallback) {
|
|
1260
|
+
if (!value)
|
|
1261
|
+
return fallback;
|
|
1262
|
+
const parsed = Number.parseFloat(value);
|
|
1263
|
+
return Number.isFinite(parsed) ? parsed : fallback;
|
|
1264
|
+
}
|
|
1265
|
+
export {
|
|
1266
|
+
experiencePlugin,
|
|
1267
|
+
typescript_default as default,
|
|
1268
|
+
OutcomeType,
|
|
1269
|
+
ExperienceType,
|
|
1270
|
+
ExperienceServiceType,
|
|
1271
|
+
ExperienceService
|
|
1272
|
+
};
|
|
1273
|
+
|
|
1274
|
+
//# debugId=332CCD4C6A890DF464756E2164756E21
|