neo4j-agent-memory 0.4.1 → 0.5.0
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/README.md +75 -2
- package/dist/cypher/auto_relate_memory_by_tags.cypher +9 -0
- package/dist/cypher/fallback_retrieve_memories.cypher +68 -0
- package/dist/cypher/get_memory_graph.cypher +25 -2
- package/dist/cypher/index.ts +2 -0
- package/dist/cypher/list_memory_edges.cypher +37 -0
- package/dist/index.cjs +109 -18
- package/dist/index.d.ts +55 -2
- package/dist/index.js +109 -18
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -52,7 +52,13 @@ const bundle = await mem.retrieveContextBundle({
|
|
|
52
52
|
prompt: "EACCES cannot create node_modules",
|
|
53
53
|
symptoms: ["eacces", "permission denied", "node_modules"],
|
|
54
54
|
tags: ["npm", "node_modules"],
|
|
55
|
-
env: { os: "macos", packageManager: "npm", container: false }
|
|
55
|
+
env: { os: "macos", packageManager: "npm", container: false },
|
|
56
|
+
fallback: {
|
|
57
|
+
enabled: true,
|
|
58
|
+
useFulltext: true,
|
|
59
|
+
useTags: true,
|
|
60
|
+
useVector: false,
|
|
61
|
+
},
|
|
56
62
|
});
|
|
57
63
|
|
|
58
64
|
const feedback = await mem.feedback({
|
|
@@ -69,7 +75,9 @@ await mem.close();
|
|
|
69
75
|
Notes:
|
|
70
76
|
- `createMemoryService` runs schema setup on init.
|
|
71
77
|
- Cypher assets are bundled at `dist/cypher` in the published package.
|
|
72
|
-
|
|
78
|
+
- `feedback()` returns updated RECALLS edge posteriors for the provided memory ids.
|
|
79
|
+
- Neutral usage: pass `neutralIds` or `updateUnratedUsed: false` to avoid penalizing retrieved-but-unrated memories.
|
|
80
|
+
- Fallback retrieval uses fulltext/tag (and optional vector) search; provide `fallback.embedding` when using vector indexes.
|
|
73
81
|
|
|
74
82
|
Auto-relate config (defaults):
|
|
75
83
|
- `enabled: true`
|
|
@@ -90,6 +98,21 @@ Auto-relate behavior:
|
|
|
90
98
|
Performance note:
|
|
91
99
|
- Auto-relate scans candidate memories with tag filtering; for large graphs, keep tags selective and consider tightening `maxCandidates` and `minSharedTags`.
|
|
92
100
|
|
|
101
|
+
Neo4j Browser params (auto-relate by tags):
|
|
102
|
+
|
|
103
|
+
```cypher
|
|
104
|
+
:param nowIso => "2026-01-04T22:07:53.086Z";
|
|
105
|
+
:param id => "mem_8cd773c2-208c-45ad-97ea-1b2337dca751";
|
|
106
|
+
:param minSharedTags => 2;
|
|
107
|
+
:param minWeight => 0.3;
|
|
108
|
+
:param maxCandidates => 10;
|
|
109
|
+
:param sameKind => false;
|
|
110
|
+
:param samePolarity => false;
|
|
111
|
+
:param allowedKinds => [];
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Note: `:param` lines are only supported in Neo4j Browser; other runners should pass parameters via the driver.
|
|
115
|
+
|
|
93
116
|
## Tool adapter (createMemoryTools)
|
|
94
117
|
|
|
95
118
|
Use the tool factory to preserve the existing tool surface used by the demo:
|
|
@@ -143,6 +166,24 @@ const graph = await mem.getMemoryGraph({
|
|
|
143
166
|
agentId: "auggie",
|
|
144
167
|
memoryIds: ["mem-1", "mem-2"],
|
|
145
168
|
includeNodes: true,
|
|
169
|
+
includeRelatedTo: true,
|
|
170
|
+
});
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
List edges for analytics/audit:
|
|
174
|
+
|
|
175
|
+
```ts
|
|
176
|
+
const edges = await mem.listMemoryEdges({ limit: 500, minStrength: 0.2 });
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
Retrieve a bundle with graph edges in one call:
|
|
180
|
+
|
|
181
|
+
```ts
|
|
182
|
+
const bundleWithGraph = await mem.retrieveContextBundleWithGraph({
|
|
183
|
+
agentId: "auggie",
|
|
184
|
+
prompt: "EACCES cannot create node_modules",
|
|
185
|
+
tags: ["npm", "node_modules"],
|
|
186
|
+
includeRelatedTo: true,
|
|
146
187
|
});
|
|
147
188
|
```
|
|
148
189
|
|
|
@@ -170,6 +211,38 @@ await mem.captureStepEpisode({
|
|
|
170
211
|
});
|
|
171
212
|
```
|
|
172
213
|
|
|
214
|
+
## Useful learning capture
|
|
215
|
+
|
|
216
|
+
```ts
|
|
217
|
+
await mem.captureUsefulLearning({
|
|
218
|
+
agentId: "auggie",
|
|
219
|
+
sessionId: "run-123",
|
|
220
|
+
useful: true,
|
|
221
|
+
learning: {
|
|
222
|
+
kind: "semantic",
|
|
223
|
+
title: "Avoid chmod 777 on node_modules",
|
|
224
|
+
content: "Use npm cache ownership fixes instead of chmod 777.",
|
|
225
|
+
tags: ["npm", "permissions"],
|
|
226
|
+
confidence: 0.8,
|
|
227
|
+
utility: 0.3,
|
|
228
|
+
},
|
|
229
|
+
});
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
## Case helpers
|
|
233
|
+
|
|
234
|
+
```ts
|
|
235
|
+
await mem.createCase({
|
|
236
|
+
title: "npm EACCES",
|
|
237
|
+
summary: "Permission denied on cache directory.",
|
|
238
|
+
outcome: "resolved",
|
|
239
|
+
symptoms: ["eacces", "permission denied"],
|
|
240
|
+
env: { os: "macos", packageManager: "npm" },
|
|
241
|
+
resolvedByMemoryIds: ["mem-1"],
|
|
242
|
+
negativeMemoryIds: [],
|
|
243
|
+
});
|
|
244
|
+
```
|
|
245
|
+
|
|
173
246
|
## Event hooks
|
|
174
247
|
|
|
175
248
|
Provide an `onMemoryEvent` callback to observe reads/writes:
|
|
@@ -7,6 +7,15 @@
|
|
|
7
7
|
// - $sameKind: Only relate to same kind if true
|
|
8
8
|
// - $samePolarity: Only relate to same polarity if true
|
|
9
9
|
// - $allowedKinds: Optional list of kinds to consider (empty = all)
|
|
10
|
+
// Neo4j Browser params (example only):
|
|
11
|
+
// :param nowIso => "2026-01-04T22:07:53.086Z";
|
|
12
|
+
// :param id => "mem_8cd773c2-208c-45ad-97ea-1b2337dca751";
|
|
13
|
+
// :param minSharedTags => 2;
|
|
14
|
+
// :param minWeight => 0.3;
|
|
15
|
+
// :param maxCandidates => 10;
|
|
16
|
+
// :param sameKind => false;
|
|
17
|
+
// :param samePolarity => false;
|
|
18
|
+
// :param allowedKinds => [];
|
|
10
19
|
WITH datetime($nowIso) AS now
|
|
11
20
|
MATCH (src:Memory {id: $id})
|
|
12
21
|
WITH src, now, coalesce(src.tags, []) AS srcTags
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
// Parameters:
|
|
2
|
+
// - $prompt: Query text
|
|
3
|
+
// - $tags: Array of tags
|
|
4
|
+
// - $kinds: Optional kinds filter
|
|
5
|
+
// - $fulltextIndex: Fulltext index name
|
|
6
|
+
// - $vectorIndex: Vector index name
|
|
7
|
+
// - $embedding: Optional embedding vector
|
|
8
|
+
// - $useFulltext: boolean
|
|
9
|
+
// - $useVector: boolean
|
|
10
|
+
// - $useTags: boolean
|
|
11
|
+
// - $fixLimit: number
|
|
12
|
+
// - $dontLimit: number
|
|
13
|
+
WITH
|
|
14
|
+
coalesce($prompt, "") AS prompt,
|
|
15
|
+
coalesce($tags, []) AS tags,
|
|
16
|
+
coalesce($kinds, []) AS kinds,
|
|
17
|
+
coalesce($fulltextIndex, "") AS fulltextIndex,
|
|
18
|
+
coalesce($vectorIndex, "") AS vectorIndex,
|
|
19
|
+
$embedding AS embedding,
|
|
20
|
+
coalesce($useFulltext, true) AS useFulltext,
|
|
21
|
+
coalesce($useVector, false) AS useVector,
|
|
22
|
+
coalesce($useTags, true) AS useTags,
|
|
23
|
+
coalesce($fixLimit, 8) AS fixLimit,
|
|
24
|
+
coalesce($dontLimit, 6) AS dontLimit
|
|
25
|
+
|
|
26
|
+
CALL {
|
|
27
|
+
WITH useFulltext, fulltextIndex, prompt
|
|
28
|
+
WHERE useFulltext = true AND fulltextIndex <> "" AND prompt <> ""
|
|
29
|
+
CALL db.index.fulltext.queryNodes(fulltextIndex, prompt) YIELD node, score
|
|
30
|
+
RETURN node AS m, score AS score
|
|
31
|
+
|
|
32
|
+
UNION
|
|
33
|
+
|
|
34
|
+
WITH useTags, tags
|
|
35
|
+
WHERE useTags = true AND size(tags) > 0
|
|
36
|
+
MATCH (m:Memory)
|
|
37
|
+
WHERE any(t IN tags WHERE t IN coalesce(m.tags, []))
|
|
38
|
+
RETURN m, 0.1 AS score
|
|
39
|
+
|
|
40
|
+
UNION
|
|
41
|
+
|
|
42
|
+
WITH useVector, vectorIndex, embedding
|
|
43
|
+
WHERE useVector = true AND vectorIndex <> "" AND embedding IS NOT NULL
|
|
44
|
+
CALL db.index.vector.queryNodes(vectorIndex, embedding, 20) YIELD node, score
|
|
45
|
+
RETURN node AS m, score AS score
|
|
46
|
+
}
|
|
47
|
+
WITH m, max(score) AS score, kinds, fixLimit, dontLimit
|
|
48
|
+
WHERE m IS NOT NULL AND (size(kinds) = 0 OR m.kind IN kinds)
|
|
49
|
+
WITH m, score, fixLimit, dontLimit
|
|
50
|
+
ORDER BY score DESC, m.updatedAt DESC
|
|
51
|
+
|
|
52
|
+
WITH collect(m {
|
|
53
|
+
.id,
|
|
54
|
+
.kind,
|
|
55
|
+
.polarity,
|
|
56
|
+
.title,
|
|
57
|
+
.content,
|
|
58
|
+
.tags,
|
|
59
|
+
.confidence,
|
|
60
|
+
.utility,
|
|
61
|
+
.updatedAt
|
|
62
|
+
}) AS rows, fixLimit, dontLimit
|
|
63
|
+
|
|
64
|
+
WITH
|
|
65
|
+
[m IN rows WHERE m.polarity <> "negative"][0..fixLimit] AS fixes,
|
|
66
|
+
[m IN rows WHERE m.polarity = "negative"][0..dontLimit] AS doNot
|
|
67
|
+
|
|
68
|
+
RETURN { fixes: fixes, doNot: doNot } AS sections;
|
|
@@ -2,10 +2,12 @@
|
|
|
2
2
|
// - $agentId: Agent id for RECALLS edges
|
|
3
3
|
// - $memoryIds: Array of memory ids
|
|
4
4
|
// - $includeNodes: Boolean to include node payloads
|
|
5
|
+
// - $includeRelatedTo: Boolean to include RELATED_TO edges
|
|
5
6
|
WITH
|
|
6
7
|
coalesce($agentId, "") AS agentId,
|
|
7
8
|
[id IN coalesce($memoryIds, []) WHERE id IS NOT NULL AND id <> ""] AS ids,
|
|
8
|
-
coalesce($includeNodes, true) AS includeNodes
|
|
9
|
+
coalesce($includeNodes, true) AS includeNodes,
|
|
10
|
+
coalesce($includeRelatedTo, false) AS includeRelatedTo
|
|
9
11
|
|
|
10
12
|
CALL (ids, includeNodes) {
|
|
11
13
|
WITH ids, includeNodes
|
|
@@ -82,4 +84,25 @@ CALL (ids) {
|
|
|
82
84
|
}) AS coUsedEdges
|
|
83
85
|
}
|
|
84
86
|
|
|
85
|
-
|
|
87
|
+
CALL (ids, includeRelatedTo) {
|
|
88
|
+
WITH ids, includeRelatedTo
|
|
89
|
+
WHERE includeRelatedTo = true
|
|
90
|
+
MATCH (m1:Memory)-[r:RELATED_TO]->(m2:Memory)
|
|
91
|
+
WHERE m1.id IN ids AND m2.id IN ids
|
|
92
|
+
RETURN collect({
|
|
93
|
+
source: m1.id,
|
|
94
|
+
target: m2.id,
|
|
95
|
+
kind: "related_to",
|
|
96
|
+
strength: r.weight,
|
|
97
|
+
evidence: 0.0,
|
|
98
|
+
updatedAt: toString(r.updatedAt)
|
|
99
|
+
}) AS relatedEdges
|
|
100
|
+
|
|
101
|
+
UNION
|
|
102
|
+
|
|
103
|
+
WITH ids, includeRelatedTo
|
|
104
|
+
WHERE includeRelatedTo = false
|
|
105
|
+
RETURN [] AS relatedEdges
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
RETURN nodes, recallEdges + coUsedEdges + relatedEdges AS edges;
|
package/dist/cypher/index.ts
CHANGED
|
@@ -23,4 +23,6 @@ export const cypher = {
|
|
|
23
23
|
autoRelateByTags: loadCypher("auto_relate_memory_by_tags.cypher"),
|
|
24
24
|
getMemoriesById: loadCypher("get_memories_by_id.cypher"),
|
|
25
25
|
getMemoryGraph: loadCypher("get_memory_graph.cypher"),
|
|
26
|
+
fallbackRetrieveMemories: loadCypher("fallback_retrieve_memories.cypher"),
|
|
27
|
+
listMemoryEdges: loadCypher("list_memory_edges.cypher"),
|
|
26
28
|
};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// Parameters:
|
|
2
|
+
// - $limit: Max edges to return
|
|
3
|
+
// - $minStrength: Minimum strength threshold
|
|
4
|
+
WITH
|
|
5
|
+
coalesce($limit, 200) AS limit,
|
|
6
|
+
coalesce($minStrength, 0.0) AS minStrength
|
|
7
|
+
|
|
8
|
+
CALL {
|
|
9
|
+
WITH minStrength
|
|
10
|
+
MATCH (m1:Memory)-[c:CO_USED_WITH]->(m2:Memory)
|
|
11
|
+
WHERE coalesce(c.strength, 0.0) >= minStrength
|
|
12
|
+
RETURN {
|
|
13
|
+
source: m1.id,
|
|
14
|
+
target: m2.id,
|
|
15
|
+
kind: "co_used_with",
|
|
16
|
+
strength: c.strength,
|
|
17
|
+
evidence: c.evidence,
|
|
18
|
+
updatedAt: toString(c.updatedAt)
|
|
19
|
+
} AS edge
|
|
20
|
+
|
|
21
|
+
UNION
|
|
22
|
+
|
|
23
|
+
WITH minStrength
|
|
24
|
+
MATCH (m1:Memory)-[r:RELATED_TO]->(m2:Memory)
|
|
25
|
+
WHERE coalesce(r.weight, 0.0) >= minStrength
|
|
26
|
+
RETURN {
|
|
27
|
+
source: m1.id,
|
|
28
|
+
target: m2.id,
|
|
29
|
+
kind: "related_to",
|
|
30
|
+
strength: r.weight,
|
|
31
|
+
evidence: 0.0,
|
|
32
|
+
updatedAt: toString(r.updatedAt)
|
|
33
|
+
} AS edge
|
|
34
|
+
}
|
|
35
|
+
WITH edge, limit
|
|
36
|
+
ORDER BY edge.strength DESC
|
|
37
|
+
RETURN collect(edge)[0..limit] AS edges;
|
package/dist/index.cjs
CHANGED
|
@@ -88,7 +88,9 @@ var cypher = {
|
|
|
88
88
|
relateConcepts: loadCypher("relate_concepts.cypher"),
|
|
89
89
|
autoRelateByTags: loadCypher("auto_relate_memory_by_tags.cypher"),
|
|
90
90
|
getMemoriesById: loadCypher("get_memories_by_id.cypher"),
|
|
91
|
-
getMemoryGraph: loadCypher("get_memory_graph.cypher")
|
|
91
|
+
getMemoryGraph: loadCypher("get_memory_graph.cypher"),
|
|
92
|
+
fallbackRetrieveMemories: loadCypher("fallback_retrieve_memories.cypher"),
|
|
93
|
+
listMemoryEdges: loadCypher("list_memory_edges.cypher")
|
|
92
94
|
};
|
|
93
95
|
|
|
94
96
|
// src/neo4j/schema.ts
|
|
@@ -278,6 +280,8 @@ var MemoryService = class {
|
|
|
278
280
|
cyAutoRelateByTags = cypher.autoRelateByTags;
|
|
279
281
|
cyGetMemoriesById = cypher.getMemoriesById;
|
|
280
282
|
cyGetMemoryGraph = cypher.getMemoryGraph;
|
|
283
|
+
cyFallbackRetrieve = cypher.fallbackRetrieveMemories;
|
|
284
|
+
cyListMemoryEdges = cypher.listMemoryEdges;
|
|
281
285
|
cyGetRecallEdges = `
|
|
282
286
|
UNWIND $ids AS id
|
|
283
287
|
MATCH (m:Memory {id:id})
|
|
@@ -365,7 +369,7 @@ var MemoryService = class {
|
|
|
365
369
|
contentHash,
|
|
366
370
|
tags,
|
|
367
371
|
confidence: clamp01(l.confidence),
|
|
368
|
-
utility: 0.2,
|
|
372
|
+
utility: typeof l.utility === "number" ? clamp01(l.utility) : 0.2,
|
|
369
373
|
// start modest; reinforce via feedback
|
|
370
374
|
triage: l.triage ? JSON.stringify(l.triage) : null,
|
|
371
375
|
antiPattern: l.antiPattern ? JSON.stringify(l.antiPattern) : null
|
|
@@ -442,6 +446,13 @@ var MemoryService = class {
|
|
|
442
446
|
await session.close();
|
|
443
447
|
}
|
|
444
448
|
}
|
|
449
|
+
/**
|
|
450
|
+
* Create a new Case with an auto-generated id if none is provided.
|
|
451
|
+
*/
|
|
452
|
+
async createCase(c) {
|
|
453
|
+
const id = c.id ?? newId("case");
|
|
454
|
+
return this.upsertCase({ ...c, id });
|
|
455
|
+
}
|
|
445
456
|
/**
|
|
446
457
|
* Retrieve a ContextBundle with separate Fix and Do-not-do sections, using case-based reasoning.
|
|
447
458
|
* The key idea: match cases by symptoms + env similarity, then pull linked memories.
|
|
@@ -467,28 +478,49 @@ var MemoryService = class {
|
|
|
467
478
|
halfLifeSeconds: this.halfLifeSeconds
|
|
468
479
|
});
|
|
469
480
|
const sections = r.records[0].get("sections");
|
|
470
|
-
const
|
|
471
|
-
id: m.id,
|
|
472
|
-
kind: m.kind,
|
|
473
|
-
polarity: m.polarity ?? "positive",
|
|
474
|
-
title: m.title,
|
|
475
|
-
content: m.content,
|
|
476
|
-
tags: m.tags ?? [],
|
|
477
|
-
confidence: m.confidence ?? 0.7,
|
|
478
|
-
utility: m.utility ?? 0.2,
|
|
479
|
-
updatedAt: m.updatedAt?.toString?.() ?? null
|
|
480
|
-
}));
|
|
481
|
-
const doNot = (sections.doNot ?? []).map((m) => ({
|
|
481
|
+
const mapSummary = (m, fallbackPolarity) => ({
|
|
482
482
|
id: m.id,
|
|
483
483
|
kind: m.kind,
|
|
484
|
-
polarity: m.polarity ??
|
|
484
|
+
polarity: m.polarity ?? fallbackPolarity,
|
|
485
485
|
title: m.title,
|
|
486
486
|
content: m.content,
|
|
487
487
|
tags: m.tags ?? [],
|
|
488
488
|
confidence: m.confidence ?? 0.7,
|
|
489
489
|
utility: m.utility ?? 0.2,
|
|
490
490
|
updatedAt: m.updatedAt?.toString?.() ?? null
|
|
491
|
-
})
|
|
491
|
+
});
|
|
492
|
+
let fixes = (sections.fixes ?? []).map((m) => mapSummary(m, "positive"));
|
|
493
|
+
let doNot = (sections.doNot ?? []).map((m) => mapSummary(m, "negative"));
|
|
494
|
+
const fallback = args.fallback ?? {};
|
|
495
|
+
const shouldFallback = fallback.enabled === true && fixes.length === 0 && doNot.length === 0;
|
|
496
|
+
if (shouldFallback) {
|
|
497
|
+
const fallbackFixLimit = fallback.limit ?? fixLimit;
|
|
498
|
+
const fallbackDontLimit = fallback.limit ?? dontLimit;
|
|
499
|
+
try {
|
|
500
|
+
const fallbackRes = await session.run(this.cyFallbackRetrieve, {
|
|
501
|
+
prompt: args.prompt ?? "",
|
|
502
|
+
tags: args.tags ?? [],
|
|
503
|
+
kinds: args.kinds ?? [],
|
|
504
|
+
fulltextIndex: this.fulltextIndex,
|
|
505
|
+
vectorIndex: this.vectorIndex,
|
|
506
|
+
embedding: fallback.embedding ?? null,
|
|
507
|
+
useFulltext: fallback.useFulltext ?? true,
|
|
508
|
+
useVector: fallback.useVector ?? false,
|
|
509
|
+
useTags: fallback.useTags ?? true,
|
|
510
|
+
fixLimit: fallbackFixLimit,
|
|
511
|
+
dontLimit: fallbackDontLimit
|
|
512
|
+
});
|
|
513
|
+
const fbSections = fallbackRes.records[0]?.get("sections");
|
|
514
|
+
fixes = (fbSections?.fixes ?? []).map((m) => mapSummary(m, "positive"));
|
|
515
|
+
doNot = (fbSections?.doNot ?? []).map((m) => mapSummary(m, "negative"));
|
|
516
|
+
} catch (err) {
|
|
517
|
+
this.emit({
|
|
518
|
+
type: "read",
|
|
519
|
+
action: "retrieveContextBundle.fallbackError",
|
|
520
|
+
meta: { message: err instanceof Error ? err.message : String(err) }
|
|
521
|
+
});
|
|
522
|
+
}
|
|
523
|
+
}
|
|
492
524
|
const allIds = [.../* @__PURE__ */ new Set([...fixes.map((x) => x.id), ...doNot.map((x) => x.id)])];
|
|
493
525
|
const edgeAfter = /* @__PURE__ */ new Map();
|
|
494
526
|
if (allIds.length > 0) {
|
|
@@ -583,7 +615,8 @@ ${m.content}`).join("");
|
|
|
583
615
|
const res = await session.run(this.cyGetMemoryGraph, {
|
|
584
616
|
agentId: args.agentId ?? null,
|
|
585
617
|
memoryIds: ids,
|
|
586
|
-
includeNodes: args.includeNodes ?? true
|
|
618
|
+
includeNodes: args.includeNodes ?? true,
|
|
619
|
+
includeRelatedTo: args.includeRelatedTo ?? false
|
|
587
620
|
});
|
|
588
621
|
const record = res.records[0];
|
|
589
622
|
const nodesRaw = record?.get("nodes") ?? [];
|
|
@@ -596,6 +629,32 @@ ${m.content}`).join("");
|
|
|
596
629
|
await session.close();
|
|
597
630
|
}
|
|
598
631
|
}
|
|
632
|
+
async listMemoryEdges(args = {}) {
|
|
633
|
+
const session = this.client.session("READ");
|
|
634
|
+
try {
|
|
635
|
+
const res = await session.run(this.cyListMemoryEdges, {
|
|
636
|
+
limit: args.limit ?? 200,
|
|
637
|
+
minStrength: args.minStrength ?? 0
|
|
638
|
+
});
|
|
639
|
+
return res.records[0]?.get("edges") ?? [];
|
|
640
|
+
} finally {
|
|
641
|
+
await session.close();
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
async retrieveContextBundleWithGraph(args) {
|
|
645
|
+
const bundle = await this.retrieveContextBundle(args);
|
|
646
|
+
const ids = [
|
|
647
|
+
...bundle.sections.fix.map((m) => m.id),
|
|
648
|
+
...bundle.sections.doNotDo.map((m) => m.id)
|
|
649
|
+
];
|
|
650
|
+
const graph = await this.getMemoryGraph({
|
|
651
|
+
agentId: args.agentId,
|
|
652
|
+
memoryIds: ids,
|
|
653
|
+
includeNodes: args.includeNodes ?? false,
|
|
654
|
+
includeRelatedTo: args.includeRelatedTo ?? false
|
|
655
|
+
});
|
|
656
|
+
return { bundle, graph };
|
|
657
|
+
}
|
|
599
658
|
async listEpisodes(args = {}) {
|
|
600
659
|
return this.listMemories({ ...args, kind: "episodic" });
|
|
601
660
|
}
|
|
@@ -626,6 +685,22 @@ ${m.content}`).join("");
|
|
|
626
685
|
this.emit({ type: "write", action: "captureEpisode", meta: { runId: args.runId, title } });
|
|
627
686
|
return result;
|
|
628
687
|
}
|
|
688
|
+
async captureUsefulLearning(args) {
|
|
689
|
+
if (args.useful === false) {
|
|
690
|
+
return { saved: [], rejected: [{ title: args.learning.title, reason: "not marked useful" }] };
|
|
691
|
+
}
|
|
692
|
+
const result = await this.saveLearnings({
|
|
693
|
+
agentId: args.agentId,
|
|
694
|
+
sessionId: args.sessionId,
|
|
695
|
+
learnings: [args.learning]
|
|
696
|
+
});
|
|
697
|
+
this.emit({
|
|
698
|
+
type: "write",
|
|
699
|
+
action: "captureUsefulLearning",
|
|
700
|
+
meta: { title: args.learning.title, savedCount: result.saved.length }
|
|
701
|
+
});
|
|
702
|
+
return result;
|
|
703
|
+
}
|
|
629
704
|
async captureStepEpisode(args) {
|
|
630
705
|
const title = `Episode ${args.workflowName} - ${args.stepName}`;
|
|
631
706
|
const base = {
|
|
@@ -655,18 +730,34 @@ ${m.content}`).join("");
|
|
|
655
730
|
const used = new Set(fb.usedIds ?? []);
|
|
656
731
|
const useful = new Set(fb.usefulIds ?? []);
|
|
657
732
|
const notUseful = new Set(fb.notUsefulIds ?? []);
|
|
733
|
+
const neutral = new Set(fb.neutralIds ?? []);
|
|
658
734
|
const prevented = new Set(fb.preventedErrorIds ?? []);
|
|
735
|
+
const updateUnratedUsed = fb.updateUnratedUsed ?? true;
|
|
659
736
|
for (const id of prevented) useful.add(id);
|
|
660
737
|
for (const id of useful) notUseful.delete(id);
|
|
738
|
+
for (const id of neutral) notUseful.delete(id);
|
|
661
739
|
for (const id of useful) used.add(id);
|
|
662
740
|
for (const id of notUseful) used.add(id);
|
|
741
|
+
for (const id of neutral) used.add(id);
|
|
663
742
|
const quality = clamp01(fb.metrics?.quality ?? 0.7);
|
|
664
743
|
const hallucRisk = clamp01(fb.metrics?.hallucinationRisk ?? 0.2);
|
|
665
744
|
const baseY = clamp01(quality - 0.7 * hallucRisk);
|
|
666
745
|
const w = 0.5 + 1.5 * quality;
|
|
667
746
|
const yById = /* @__PURE__ */ new Map();
|
|
668
747
|
for (const id of used) {
|
|
669
|
-
|
|
748
|
+
if (useful.has(id)) {
|
|
749
|
+
yById.set(id, baseY);
|
|
750
|
+
continue;
|
|
751
|
+
}
|
|
752
|
+
if (notUseful.has(id)) {
|
|
753
|
+
yById.set(id, 0);
|
|
754
|
+
continue;
|
|
755
|
+
}
|
|
756
|
+
if (neutral.has(id) || !updateUnratedUsed) {
|
|
757
|
+
yById.set(id, 0.5);
|
|
758
|
+
continue;
|
|
759
|
+
}
|
|
760
|
+
yById.set(id, 0);
|
|
670
761
|
}
|
|
671
762
|
const items = [...used].map((memoryId) => ({
|
|
672
763
|
memoryId,
|
package/dist/index.d.ts
CHANGED
|
@@ -71,7 +71,7 @@ interface MemorySummary {
|
|
|
71
71
|
interface MemoryGraphEdge {
|
|
72
72
|
source: string;
|
|
73
73
|
target: string;
|
|
74
|
-
kind: "recalls" | "co_used_with";
|
|
74
|
+
kind: "recalls" | "co_used_with" | "related_to";
|
|
75
75
|
strength: number;
|
|
76
76
|
evidence: number;
|
|
77
77
|
updatedAt?: string | null;
|
|
@@ -106,6 +106,14 @@ interface RetrieveContextArgs {
|
|
|
106
106
|
fixLimit?: number;
|
|
107
107
|
dontLimit?: number;
|
|
108
108
|
nowIso?: string;
|
|
109
|
+
fallback?: {
|
|
110
|
+
enabled?: boolean;
|
|
111
|
+
limit?: number;
|
|
112
|
+
useFulltext?: boolean;
|
|
113
|
+
useVector?: boolean;
|
|
114
|
+
useTags?: boolean;
|
|
115
|
+
embedding?: number[];
|
|
116
|
+
};
|
|
109
117
|
}
|
|
110
118
|
interface ListMemoriesArgs {
|
|
111
119
|
kind?: MemoryKind;
|
|
@@ -119,6 +127,7 @@ interface GetMemoryGraphArgs {
|
|
|
119
127
|
agentId?: string;
|
|
120
128
|
memoryIds: string[];
|
|
121
129
|
includeNodes?: boolean;
|
|
130
|
+
includeRelatedTo?: boolean;
|
|
122
131
|
}
|
|
123
132
|
interface BetaEdge {
|
|
124
133
|
a: number;
|
|
@@ -160,6 +169,8 @@ interface MemoryFeedback {
|
|
|
160
169
|
usedIds: string[];
|
|
161
170
|
usefulIds: string[];
|
|
162
171
|
notUsefulIds: string[];
|
|
172
|
+
neutralIds?: string[];
|
|
173
|
+
updateUnratedUsed?: boolean;
|
|
163
174
|
preventedErrorIds?: string[];
|
|
164
175
|
metrics?: FeedbackMetrics;
|
|
165
176
|
notes?: string;
|
|
@@ -170,6 +181,34 @@ interface MemoryFeedbackResult {
|
|
|
170
181
|
edge: BetaEdge;
|
|
171
182
|
}>;
|
|
172
183
|
}
|
|
184
|
+
interface ListMemoryEdgesArgs {
|
|
185
|
+
limit?: number;
|
|
186
|
+
minStrength?: number;
|
|
187
|
+
}
|
|
188
|
+
interface MemoryEdgeExport {
|
|
189
|
+
source: string;
|
|
190
|
+
target: string;
|
|
191
|
+
kind: "co_used_with" | "related_to";
|
|
192
|
+
strength: number;
|
|
193
|
+
evidence: number;
|
|
194
|
+
updatedAt?: string | null;
|
|
195
|
+
}
|
|
196
|
+
interface RetrieveContextBundleWithGraphArgs extends RetrieveContextArgs {
|
|
197
|
+
includeNodes?: boolean;
|
|
198
|
+
includeRelatedTo?: boolean;
|
|
199
|
+
}
|
|
200
|
+
interface ContextBundleWithGraph {
|
|
201
|
+
bundle: ContextBundle;
|
|
202
|
+
graph: MemoryGraphResponse;
|
|
203
|
+
}
|
|
204
|
+
interface CaptureUsefulLearningArgs {
|
|
205
|
+
agentId: string;
|
|
206
|
+
sessionId?: string;
|
|
207
|
+
useful?: boolean;
|
|
208
|
+
learning: LearningCandidate & {
|
|
209
|
+
utility?: number;
|
|
210
|
+
};
|
|
211
|
+
}
|
|
173
212
|
interface CaptureEpisodeArgs {
|
|
174
213
|
agentId: string;
|
|
175
214
|
runId: string;
|
|
@@ -195,6 +234,7 @@ interface LearningCandidate {
|
|
|
195
234
|
content: string;
|
|
196
235
|
tags: string[];
|
|
197
236
|
confidence: number;
|
|
237
|
+
utility?: number;
|
|
198
238
|
signals?: MemoryRecord["signals"];
|
|
199
239
|
env?: EnvironmentFingerprint;
|
|
200
240
|
triage?: MemoryTriage;
|
|
@@ -271,6 +311,8 @@ declare class MemoryService {
|
|
|
271
311
|
private cyAutoRelateByTags;
|
|
272
312
|
private cyGetMemoriesById;
|
|
273
313
|
private cyGetMemoryGraph;
|
|
314
|
+
private cyFallbackRetrieve;
|
|
315
|
+
private cyListMemoryEdges;
|
|
274
316
|
private cyGetRecallEdges;
|
|
275
317
|
constructor(cfg: MemoryServiceConfig);
|
|
276
318
|
init(): Promise<void>;
|
|
@@ -291,6 +333,12 @@ declare class MemoryService {
|
|
|
291
333
|
* Upsert an episodic Case (case-based reasoning) that links symptoms + env + resolved_by + negative memories.
|
|
292
334
|
*/
|
|
293
335
|
upsertCase(c: CaseRecord): Promise<string>;
|
|
336
|
+
/**
|
|
337
|
+
* Create a new Case with an auto-generated id if none is provided.
|
|
338
|
+
*/
|
|
339
|
+
createCase(c: Omit<CaseRecord, "id"> & {
|
|
340
|
+
id?: string;
|
|
341
|
+
}): Promise<string>;
|
|
294
342
|
/**
|
|
295
343
|
* Retrieve a ContextBundle with separate Fix and Do-not-do sections, using case-based reasoning.
|
|
296
344
|
* The key idea: match cases by symptoms + env similarity, then pull linked memories.
|
|
@@ -299,6 +347,8 @@ declare class MemoryService {
|
|
|
299
347
|
listMemories(args?: ListMemoriesArgs): Promise<MemorySummary[]>;
|
|
300
348
|
getMemoriesById(args: GetMemoriesByIdArgs): Promise<MemoryRecord[]>;
|
|
301
349
|
getMemoryGraph(args: GetMemoryGraphArgs): Promise<MemoryGraphResponse>;
|
|
350
|
+
listMemoryEdges(args?: ListMemoryEdgesArgs): Promise<MemoryEdgeExport[]>;
|
|
351
|
+
retrieveContextBundleWithGraph(args: RetrieveContextBundleWithGraphArgs): Promise<ContextBundleWithGraph>;
|
|
302
352
|
listEpisodes(args?: Omit<ListMemoriesArgs, "kind">): Promise<MemorySummary[]>;
|
|
303
353
|
listSkills(args?: Omit<ListMemoriesArgs, "kind">): Promise<MemorySummary[]>;
|
|
304
354
|
listConcepts(args?: Omit<ListMemoriesArgs, "kind">): Promise<MemorySummary[]>;
|
|
@@ -308,6 +358,7 @@ declare class MemoryService {
|
|
|
308
358
|
weight?: number;
|
|
309
359
|
}): Promise<void>;
|
|
310
360
|
captureEpisode(args: CaptureEpisodeArgs): Promise<SaveLearningResult>;
|
|
361
|
+
captureUsefulLearning(args: CaptureUsefulLearningArgs): Promise<SaveLearningResult>;
|
|
311
362
|
captureStepEpisode(args: CaptureStepEpisodeArgs): Promise<SaveLearningResult>;
|
|
312
363
|
/**
|
|
313
364
|
* Reinforce/degrade agent->memory association weights using a single batched Cypher query.
|
|
@@ -354,6 +405,8 @@ declare const cypher: {
|
|
|
354
405
|
autoRelateByTags: string;
|
|
355
406
|
getMemoriesById: string;
|
|
356
407
|
getMemoryGraph: string;
|
|
408
|
+
fallbackRetrieveMemories: string;
|
|
409
|
+
listMemoryEdges: string;
|
|
357
410
|
};
|
|
358
411
|
|
|
359
412
|
declare function createMemoryTools(service: MemoryService): MemoryToolSet;
|
|
@@ -364,4 +417,4 @@ declare function newId(prefix: string): string;
|
|
|
364
417
|
declare function normaliseSymptom(s: string): string;
|
|
365
418
|
declare function envHash(env: EnvironmentFingerprint): string;
|
|
366
419
|
|
|
367
|
-
export { type AutoRelateConfig, type BetaEdge, type CaptureEpisodeArgs, type CaptureStepEpisodeArgs, type CaseRecord, type ContextBundle, type ContextMemoryBase, type ContextMemorySummary, type DistilledInvariant, type EnvironmentFingerprint, type FeedbackMetrics, type GetMemoriesByIdArgs, type GetMemoryGraphArgs, type LearningCandidate, type ListMemoriesArgs, type MemoryEvent, type MemoryFeedback, type MemoryFeedbackResult, type MemoryGraphEdge, type MemoryGraphResponse, type MemoryKind, type MemoryPolarity, type MemoryRecord, MemoryService, type MemoryServiceConfig, type MemorySummary, type MemoryToolDefinition, type MemoryToolName, type MemoryToolSet, type MemoryTriage, Neo4jClient, type Neo4jClientConfig, type RetrieveContextArgs, type SaveLearningRequest, type SaveLearningResult, canonicaliseForHash, createMemoryService, createMemoryTools, cypher, ensureSchema, envHash, loadCypher, migrate, newId, normaliseSymptom, schemaVersion, sha256Hex };
|
|
420
|
+
export { type AutoRelateConfig, type BetaEdge, type CaptureEpisodeArgs, type CaptureStepEpisodeArgs, type CaptureUsefulLearningArgs, type CaseRecord, type ContextBundle, type ContextBundleWithGraph, type ContextMemoryBase, type ContextMemorySummary, type DistilledInvariant, type EnvironmentFingerprint, type FeedbackMetrics, type GetMemoriesByIdArgs, type GetMemoryGraphArgs, type LearningCandidate, type ListMemoriesArgs, type ListMemoryEdgesArgs, type MemoryEdgeExport, type MemoryEvent, type MemoryFeedback, type MemoryFeedbackResult, type MemoryGraphEdge, type MemoryGraphResponse, type MemoryKind, type MemoryPolarity, type MemoryRecord, MemoryService, type MemoryServiceConfig, type MemorySummary, type MemoryToolDefinition, type MemoryToolName, type MemoryToolSet, type MemoryTriage, Neo4jClient, type Neo4jClientConfig, type RetrieveContextArgs, type RetrieveContextBundleWithGraphArgs, type SaveLearningRequest, type SaveLearningResult, canonicaliseForHash, createMemoryService, createMemoryTools, cypher, ensureSchema, envHash, loadCypher, migrate, newId, normaliseSymptom, schemaVersion, sha256Hex };
|
package/dist/index.js
CHANGED
|
@@ -38,7 +38,9 @@ var cypher = {
|
|
|
38
38
|
relateConcepts: loadCypher("relate_concepts.cypher"),
|
|
39
39
|
autoRelateByTags: loadCypher("auto_relate_memory_by_tags.cypher"),
|
|
40
40
|
getMemoriesById: loadCypher("get_memories_by_id.cypher"),
|
|
41
|
-
getMemoryGraph: loadCypher("get_memory_graph.cypher")
|
|
41
|
+
getMemoryGraph: loadCypher("get_memory_graph.cypher"),
|
|
42
|
+
fallbackRetrieveMemories: loadCypher("fallback_retrieve_memories.cypher"),
|
|
43
|
+
listMemoryEdges: loadCypher("list_memory_edges.cypher")
|
|
42
44
|
};
|
|
43
45
|
|
|
44
46
|
// src/neo4j/schema.ts
|
|
@@ -228,6 +230,8 @@ var MemoryService = class {
|
|
|
228
230
|
cyAutoRelateByTags = cypher.autoRelateByTags;
|
|
229
231
|
cyGetMemoriesById = cypher.getMemoriesById;
|
|
230
232
|
cyGetMemoryGraph = cypher.getMemoryGraph;
|
|
233
|
+
cyFallbackRetrieve = cypher.fallbackRetrieveMemories;
|
|
234
|
+
cyListMemoryEdges = cypher.listMemoryEdges;
|
|
231
235
|
cyGetRecallEdges = `
|
|
232
236
|
UNWIND $ids AS id
|
|
233
237
|
MATCH (m:Memory {id:id})
|
|
@@ -315,7 +319,7 @@ var MemoryService = class {
|
|
|
315
319
|
contentHash,
|
|
316
320
|
tags,
|
|
317
321
|
confidence: clamp01(l.confidence),
|
|
318
|
-
utility: 0.2,
|
|
322
|
+
utility: typeof l.utility === "number" ? clamp01(l.utility) : 0.2,
|
|
319
323
|
// start modest; reinforce via feedback
|
|
320
324
|
triage: l.triage ? JSON.stringify(l.triage) : null,
|
|
321
325
|
antiPattern: l.antiPattern ? JSON.stringify(l.antiPattern) : null
|
|
@@ -392,6 +396,13 @@ var MemoryService = class {
|
|
|
392
396
|
await session.close();
|
|
393
397
|
}
|
|
394
398
|
}
|
|
399
|
+
/**
|
|
400
|
+
* Create a new Case with an auto-generated id if none is provided.
|
|
401
|
+
*/
|
|
402
|
+
async createCase(c) {
|
|
403
|
+
const id = c.id ?? newId("case");
|
|
404
|
+
return this.upsertCase({ ...c, id });
|
|
405
|
+
}
|
|
395
406
|
/**
|
|
396
407
|
* Retrieve a ContextBundle with separate Fix and Do-not-do sections, using case-based reasoning.
|
|
397
408
|
* The key idea: match cases by symptoms + env similarity, then pull linked memories.
|
|
@@ -417,28 +428,49 @@ var MemoryService = class {
|
|
|
417
428
|
halfLifeSeconds: this.halfLifeSeconds
|
|
418
429
|
});
|
|
419
430
|
const sections = r.records[0].get("sections");
|
|
420
|
-
const
|
|
421
|
-
id: m.id,
|
|
422
|
-
kind: m.kind,
|
|
423
|
-
polarity: m.polarity ?? "positive",
|
|
424
|
-
title: m.title,
|
|
425
|
-
content: m.content,
|
|
426
|
-
tags: m.tags ?? [],
|
|
427
|
-
confidence: m.confidence ?? 0.7,
|
|
428
|
-
utility: m.utility ?? 0.2,
|
|
429
|
-
updatedAt: m.updatedAt?.toString?.() ?? null
|
|
430
|
-
}));
|
|
431
|
-
const doNot = (sections.doNot ?? []).map((m) => ({
|
|
431
|
+
const mapSummary = (m, fallbackPolarity) => ({
|
|
432
432
|
id: m.id,
|
|
433
433
|
kind: m.kind,
|
|
434
|
-
polarity: m.polarity ??
|
|
434
|
+
polarity: m.polarity ?? fallbackPolarity,
|
|
435
435
|
title: m.title,
|
|
436
436
|
content: m.content,
|
|
437
437
|
tags: m.tags ?? [],
|
|
438
438
|
confidence: m.confidence ?? 0.7,
|
|
439
439
|
utility: m.utility ?? 0.2,
|
|
440
440
|
updatedAt: m.updatedAt?.toString?.() ?? null
|
|
441
|
-
})
|
|
441
|
+
});
|
|
442
|
+
let fixes = (sections.fixes ?? []).map((m) => mapSummary(m, "positive"));
|
|
443
|
+
let doNot = (sections.doNot ?? []).map((m) => mapSummary(m, "negative"));
|
|
444
|
+
const fallback = args.fallback ?? {};
|
|
445
|
+
const shouldFallback = fallback.enabled === true && fixes.length === 0 && doNot.length === 0;
|
|
446
|
+
if (shouldFallback) {
|
|
447
|
+
const fallbackFixLimit = fallback.limit ?? fixLimit;
|
|
448
|
+
const fallbackDontLimit = fallback.limit ?? dontLimit;
|
|
449
|
+
try {
|
|
450
|
+
const fallbackRes = await session.run(this.cyFallbackRetrieve, {
|
|
451
|
+
prompt: args.prompt ?? "",
|
|
452
|
+
tags: args.tags ?? [],
|
|
453
|
+
kinds: args.kinds ?? [],
|
|
454
|
+
fulltextIndex: this.fulltextIndex,
|
|
455
|
+
vectorIndex: this.vectorIndex,
|
|
456
|
+
embedding: fallback.embedding ?? null,
|
|
457
|
+
useFulltext: fallback.useFulltext ?? true,
|
|
458
|
+
useVector: fallback.useVector ?? false,
|
|
459
|
+
useTags: fallback.useTags ?? true,
|
|
460
|
+
fixLimit: fallbackFixLimit,
|
|
461
|
+
dontLimit: fallbackDontLimit
|
|
462
|
+
});
|
|
463
|
+
const fbSections = fallbackRes.records[0]?.get("sections");
|
|
464
|
+
fixes = (fbSections?.fixes ?? []).map((m) => mapSummary(m, "positive"));
|
|
465
|
+
doNot = (fbSections?.doNot ?? []).map((m) => mapSummary(m, "negative"));
|
|
466
|
+
} catch (err) {
|
|
467
|
+
this.emit({
|
|
468
|
+
type: "read",
|
|
469
|
+
action: "retrieveContextBundle.fallbackError",
|
|
470
|
+
meta: { message: err instanceof Error ? err.message : String(err) }
|
|
471
|
+
});
|
|
472
|
+
}
|
|
473
|
+
}
|
|
442
474
|
const allIds = [.../* @__PURE__ */ new Set([...fixes.map((x) => x.id), ...doNot.map((x) => x.id)])];
|
|
443
475
|
const edgeAfter = /* @__PURE__ */ new Map();
|
|
444
476
|
if (allIds.length > 0) {
|
|
@@ -533,7 +565,8 @@ ${m.content}`).join("");
|
|
|
533
565
|
const res = await session.run(this.cyGetMemoryGraph, {
|
|
534
566
|
agentId: args.agentId ?? null,
|
|
535
567
|
memoryIds: ids,
|
|
536
|
-
includeNodes: args.includeNodes ?? true
|
|
568
|
+
includeNodes: args.includeNodes ?? true,
|
|
569
|
+
includeRelatedTo: args.includeRelatedTo ?? false
|
|
537
570
|
});
|
|
538
571
|
const record = res.records[0];
|
|
539
572
|
const nodesRaw = record?.get("nodes") ?? [];
|
|
@@ -546,6 +579,32 @@ ${m.content}`).join("");
|
|
|
546
579
|
await session.close();
|
|
547
580
|
}
|
|
548
581
|
}
|
|
582
|
+
async listMemoryEdges(args = {}) {
|
|
583
|
+
const session = this.client.session("READ");
|
|
584
|
+
try {
|
|
585
|
+
const res = await session.run(this.cyListMemoryEdges, {
|
|
586
|
+
limit: args.limit ?? 200,
|
|
587
|
+
minStrength: args.minStrength ?? 0
|
|
588
|
+
});
|
|
589
|
+
return res.records[0]?.get("edges") ?? [];
|
|
590
|
+
} finally {
|
|
591
|
+
await session.close();
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
async retrieveContextBundleWithGraph(args) {
|
|
595
|
+
const bundle = await this.retrieveContextBundle(args);
|
|
596
|
+
const ids = [
|
|
597
|
+
...bundle.sections.fix.map((m) => m.id),
|
|
598
|
+
...bundle.sections.doNotDo.map((m) => m.id)
|
|
599
|
+
];
|
|
600
|
+
const graph = await this.getMemoryGraph({
|
|
601
|
+
agentId: args.agentId,
|
|
602
|
+
memoryIds: ids,
|
|
603
|
+
includeNodes: args.includeNodes ?? false,
|
|
604
|
+
includeRelatedTo: args.includeRelatedTo ?? false
|
|
605
|
+
});
|
|
606
|
+
return { bundle, graph };
|
|
607
|
+
}
|
|
549
608
|
async listEpisodes(args = {}) {
|
|
550
609
|
return this.listMemories({ ...args, kind: "episodic" });
|
|
551
610
|
}
|
|
@@ -576,6 +635,22 @@ ${m.content}`).join("");
|
|
|
576
635
|
this.emit({ type: "write", action: "captureEpisode", meta: { runId: args.runId, title } });
|
|
577
636
|
return result;
|
|
578
637
|
}
|
|
638
|
+
async captureUsefulLearning(args) {
|
|
639
|
+
if (args.useful === false) {
|
|
640
|
+
return { saved: [], rejected: [{ title: args.learning.title, reason: "not marked useful" }] };
|
|
641
|
+
}
|
|
642
|
+
const result = await this.saveLearnings({
|
|
643
|
+
agentId: args.agentId,
|
|
644
|
+
sessionId: args.sessionId,
|
|
645
|
+
learnings: [args.learning]
|
|
646
|
+
});
|
|
647
|
+
this.emit({
|
|
648
|
+
type: "write",
|
|
649
|
+
action: "captureUsefulLearning",
|
|
650
|
+
meta: { title: args.learning.title, savedCount: result.saved.length }
|
|
651
|
+
});
|
|
652
|
+
return result;
|
|
653
|
+
}
|
|
579
654
|
async captureStepEpisode(args) {
|
|
580
655
|
const title = `Episode ${args.workflowName} - ${args.stepName}`;
|
|
581
656
|
const base = {
|
|
@@ -605,18 +680,34 @@ ${m.content}`).join("");
|
|
|
605
680
|
const used = new Set(fb.usedIds ?? []);
|
|
606
681
|
const useful = new Set(fb.usefulIds ?? []);
|
|
607
682
|
const notUseful = new Set(fb.notUsefulIds ?? []);
|
|
683
|
+
const neutral = new Set(fb.neutralIds ?? []);
|
|
608
684
|
const prevented = new Set(fb.preventedErrorIds ?? []);
|
|
685
|
+
const updateUnratedUsed = fb.updateUnratedUsed ?? true;
|
|
609
686
|
for (const id of prevented) useful.add(id);
|
|
610
687
|
for (const id of useful) notUseful.delete(id);
|
|
688
|
+
for (const id of neutral) notUseful.delete(id);
|
|
611
689
|
for (const id of useful) used.add(id);
|
|
612
690
|
for (const id of notUseful) used.add(id);
|
|
691
|
+
for (const id of neutral) used.add(id);
|
|
613
692
|
const quality = clamp01(fb.metrics?.quality ?? 0.7);
|
|
614
693
|
const hallucRisk = clamp01(fb.metrics?.hallucinationRisk ?? 0.2);
|
|
615
694
|
const baseY = clamp01(quality - 0.7 * hallucRisk);
|
|
616
695
|
const w = 0.5 + 1.5 * quality;
|
|
617
696
|
const yById = /* @__PURE__ */ new Map();
|
|
618
697
|
for (const id of used) {
|
|
619
|
-
|
|
698
|
+
if (useful.has(id)) {
|
|
699
|
+
yById.set(id, baseY);
|
|
700
|
+
continue;
|
|
701
|
+
}
|
|
702
|
+
if (notUseful.has(id)) {
|
|
703
|
+
yById.set(id, 0);
|
|
704
|
+
continue;
|
|
705
|
+
}
|
|
706
|
+
if (neutral.has(id) || !updateUnratedUsed) {
|
|
707
|
+
yById.set(id, 0.5);
|
|
708
|
+
continue;
|
|
709
|
+
}
|
|
710
|
+
yById.set(id, 0);
|
|
620
711
|
}
|
|
621
712
|
const items = [...used].map((memoryId) => ({
|
|
622
713
|
memoryId,
|