neo4j-agent-memory 0.3.19 → 0.4.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.
- package/README.md +49 -2
- package/dist/cypher/auto_relate_memory_by_tags.cypher +45 -0
- package/dist/cypher/feedback_batch.cypher +20 -26
- package/dist/cypher/get_memories_by_id.cypher +41 -0
- package/dist/cypher/get_memory_graph.cypher +85 -0
- package/dist/cypher/index.ts +3 -0
- package/dist/cypher/schema.cypher +3 -0
- package/dist/index.cjs +127 -3
- package/dist/index.d.ts +56 -9
- package/dist/index.js +127 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -35,7 +35,16 @@ const mem = await createMemoryService({
|
|
|
35
35
|
neo4j: { uri, username, password },
|
|
36
36
|
vectorIndex: "memoryEmbedding",
|
|
37
37
|
fulltextIndex: "memoryText",
|
|
38
|
-
halfLifeSeconds: 30 * 24 * 3600
|
|
38
|
+
halfLifeSeconds: 30 * 24 * 3600,
|
|
39
|
+
autoRelate: {
|
|
40
|
+
enabled: true,
|
|
41
|
+
minSharedTags: 2,
|
|
42
|
+
minWeight: 0.2,
|
|
43
|
+
maxCandidates: 12,
|
|
44
|
+
sameKind: true,
|
|
45
|
+
samePolarity: true,
|
|
46
|
+
allowedKinds: ["semantic", "procedural"]
|
|
47
|
+
}
|
|
39
48
|
});
|
|
40
49
|
|
|
41
50
|
const bundle = await mem.retrieveContextBundle({
|
|
@@ -46,7 +55,7 @@ const bundle = await mem.retrieveContextBundle({
|
|
|
46
55
|
env: { os: "macos", packageManager: "npm", container: false }
|
|
47
56
|
});
|
|
48
57
|
|
|
49
|
-
await mem.feedback({
|
|
58
|
+
const feedback = await mem.feedback({
|
|
50
59
|
agentId: "auggie",
|
|
51
60
|
sessionId: bundle.sessionId,
|
|
52
61
|
usedIds: bundle.sections.fix.map((m) => m.id),
|
|
@@ -60,6 +69,26 @@ await mem.close();
|
|
|
60
69
|
Notes:
|
|
61
70
|
- `createMemoryService` runs schema setup on init.
|
|
62
71
|
- Cypher assets are bundled at `dist/cypher` in the published package.
|
|
72
|
+
- `feedback()` returns updated RECALLS edge posteriors for the provided memory ids.
|
|
73
|
+
|
|
74
|
+
Auto-relate config (defaults):
|
|
75
|
+
- `enabled: true`
|
|
76
|
+
- `minSharedTags: 2`
|
|
77
|
+
- `minWeight: 0.2`
|
|
78
|
+
- `maxCandidates: 12`
|
|
79
|
+
- `sameKind: true`
|
|
80
|
+
- `samePolarity: true`
|
|
81
|
+
- `allowedKinds: ["semantic", "procedural"]`
|
|
82
|
+
|
|
83
|
+
Auto-relate behavior:
|
|
84
|
+
- Uses tag overlap with Jaccard weight (`shared / (a + b - shared)`).
|
|
85
|
+
- Runs only for newly inserted memories (skips deduped).
|
|
86
|
+
- Applies filters for `sameKind`, `samePolarity`, and `allowedKinds`.
|
|
87
|
+
- Requires `minSharedTags` and `minWeight` to pass before linking.
|
|
88
|
+
- Limits to `maxCandidates` highest-weight neighbors.
|
|
89
|
+
|
|
90
|
+
Performance note:
|
|
91
|
+
- Auto-relate scans candidate memories with tag filtering; for large graphs, keep tags selective and consider tightening `maxCandidates` and `minSharedTags`.
|
|
63
92
|
|
|
64
93
|
## Tool adapter (createMemoryTools)
|
|
65
94
|
|
|
@@ -99,6 +128,24 @@ const skills = await mem.listSkills({ agentId: "auggie" });
|
|
|
99
128
|
const concepts = await mem.listConcepts({ agentId: "auggie" });
|
|
100
129
|
```
|
|
101
130
|
|
|
131
|
+
## Graph APIs
|
|
132
|
+
|
|
133
|
+
Fetch full memory records by id:
|
|
134
|
+
|
|
135
|
+
```ts
|
|
136
|
+
const records = await mem.getMemoriesById({ ids: ["mem-1", "mem-2"] });
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Retrieve a weighted subgraph for UI maps:
|
|
140
|
+
|
|
141
|
+
```ts
|
|
142
|
+
const graph = await mem.getMemoryGraph({
|
|
143
|
+
agentId: "auggie",
|
|
144
|
+
memoryIds: ["mem-1", "mem-2"],
|
|
145
|
+
includeNodes: true,
|
|
146
|
+
});
|
|
147
|
+
```
|
|
148
|
+
|
|
102
149
|
## Episodic capture helpers
|
|
103
150
|
|
|
104
151
|
```ts
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// Parameters:
|
|
2
|
+
// - $id: Source memory id
|
|
3
|
+
// - $nowIso: ISO timestamp
|
|
4
|
+
// - $minSharedTags: Minimum overlapping tags
|
|
5
|
+
// - $minWeight: Minimum Jaccard weight to relate
|
|
6
|
+
// - $maxCandidates: Max related memories to link
|
|
7
|
+
// - $sameKind: Only relate to same kind if true
|
|
8
|
+
// - $samePolarity: Only relate to same polarity if true
|
|
9
|
+
// - $allowedKinds: Optional list of kinds to consider (empty = all)
|
|
10
|
+
WITH datetime($nowIso) AS now
|
|
11
|
+
MATCH (src:Memory {id: $id})
|
|
12
|
+
WITH src, now, coalesce(src.tags, []) AS srcTags
|
|
13
|
+
MATCH (m:Memory)
|
|
14
|
+
WHERE m.id <> src.id
|
|
15
|
+
AND (NOT $sameKind OR m.kind = src.kind)
|
|
16
|
+
AND (NOT $samePolarity OR m.polarity = src.polarity)
|
|
17
|
+
AND (size($allowedKinds) = 0 OR m.kind IN $allowedKinds)
|
|
18
|
+
WITH
|
|
19
|
+
src,
|
|
20
|
+
now,
|
|
21
|
+
srcTags,
|
|
22
|
+
m,
|
|
23
|
+
size([t IN srcTags WHERE t IN coalesce(m.tags, [])]) AS shared,
|
|
24
|
+
size(srcTags) AS aSize,
|
|
25
|
+
size(coalesce(m.tags, [])) AS bSize
|
|
26
|
+
WHERE shared >= $minSharedTags
|
|
27
|
+
WITH
|
|
28
|
+
src,
|
|
29
|
+
now,
|
|
30
|
+
m,
|
|
31
|
+
shared,
|
|
32
|
+
CASE
|
|
33
|
+
WHEN (aSize + bSize - shared) = 0 THEN 0.0
|
|
34
|
+
ELSE toFloat(shared) / (aSize + bSize - shared)
|
|
35
|
+
END AS weight
|
|
36
|
+
WHERE weight >= $minWeight
|
|
37
|
+
ORDER BY weight DESC, shared DESC
|
|
38
|
+
LIMIT $maxCandidates
|
|
39
|
+
MERGE (src)-[r:RELATED_TO]->(m)
|
|
40
|
+
ON CREATE SET r.weight = weight, r.createdAt = now, r.updatedAt = now
|
|
41
|
+
ON MATCH SET r.weight = weight, r.updatedAt = now
|
|
42
|
+
MERGE (m)-[r2:RELATED_TO]->(src)
|
|
43
|
+
ON CREATE SET r2.weight = weight, r2.createdAt = now, r2.updatedAt = now
|
|
44
|
+
ON MATCH SET r2.weight = weight, r2.updatedAt = now
|
|
45
|
+
RETURN count(*) AS related;
|
|
@@ -4,30 +4,19 @@
|
|
|
4
4
|
// halfLifeSeconds: 86400,
|
|
5
5
|
// aMin: 1.0,
|
|
6
6
|
// bMin: 1.0,
|
|
7
|
-
// w: 1.0,
|
|
8
|
-
// y: 1.0,
|
|
9
7
|
// items: [
|
|
10
|
-
// "mem_8cd773c2-208c-45ad-97ea-1b2337dca751",
|
|
11
|
-
// "mem_64fbcc73-0b5c-4041-8b6b-66514ffaf1d0",
|
|
12
|
-
// "mem_e55b80e2-6156-47bc-a964-9aef596f74a6",
|
|
13
|
-
// "mem_13189119-e3ad-4769-9f2b-2d3e3b8bc07f",
|
|
14
|
-
// "mem_137c3ff7-a81d-453d-8048-5c1b736db6ca",
|
|
15
|
-
// "mem_c0ea5643-3c61-4607-ba9f-67f4c2bb01ee",
|
|
16
|
-
// "mem_aa15e7b6-7f94-445a-9c52-3eb24a315215",
|
|
17
|
-
// "mem_e93e7bb8-b43c-44f5-a5d3-87545d67be59",
|
|
18
|
-
// "mem_b81e3709-35ae-4cab-931d-612dcc2cd43d",
|
|
19
|
-
// "mem_ce8a5b62-7ddb-4618-8a6c-93ed3b425f27",
|
|
20
|
-
// "mem_737df25b-7944-4dee-a7aa-86af93567663"
|
|
8
|
+
// { memoryId: "mem_8cd773c2-208c-45ad-97ea-1b2337dca751", w: 1.0, y: 1.0 },
|
|
9
|
+
// { memoryId: "mem_64fbcc73-0b5c-4041-8b6b-66514ffaf1d0", w: 1.0, y: 0.0 }
|
|
21
10
|
// ]
|
|
22
11
|
// }
|
|
23
12
|
WITH datetime($nowIso) AS now
|
|
24
13
|
|
|
25
|
-
UNWIND $items AS
|
|
26
|
-
WITH now,
|
|
27
|
-
WHERE memoryId IS NOT NULL AND memoryId <> ""
|
|
14
|
+
UNWIND $items AS item
|
|
15
|
+
WITH now, item
|
|
16
|
+
WHERE item.memoryId IS NOT NULL AND item.memoryId <> ""
|
|
28
17
|
|
|
29
18
|
MATCH (a:Agent {id: $agentId})
|
|
30
|
-
MATCH (m:Memory {id: memoryId})
|
|
19
|
+
MATCH (m:Memory {id: item.memoryId})
|
|
31
20
|
|
|
32
21
|
MERGE (a)-[r:RECALLS]->(m)
|
|
33
22
|
ON CREATE SET
|
|
@@ -38,25 +27,25 @@ ON CREATE SET
|
|
|
38
27
|
r.successes = 0,
|
|
39
28
|
r.failures = 0
|
|
40
29
|
|
|
41
|
-
WITH now, r,
|
|
30
|
+
WITH now, r, item,
|
|
42
31
|
CASE
|
|
43
32
|
WHEN duration.inSeconds(coalesce(r.updatedAt, now), now).seconds > 0
|
|
44
33
|
THEN duration.inSeconds(coalesce(r.updatedAt, now), now).seconds
|
|
45
34
|
ELSE 0
|
|
46
35
|
END AS dt
|
|
47
36
|
|
|
48
|
-
WITH now, r,
|
|
37
|
+
WITH now, r, item,
|
|
49
38
|
0.5 ^ (dt / $halfLifeSeconds) AS gamma,
|
|
50
39
|
coalesce(r.a, $aMin) AS aPrev,
|
|
51
40
|
coalesce(r.b, $bMin) AS bPrev
|
|
52
41
|
|
|
53
|
-
WITH now, r, gamma,
|
|
42
|
+
WITH now, r, item, gamma,
|
|
54
43
|
($aMin + gamma * (aPrev - $aMin)) AS a0,
|
|
55
44
|
($bMin + gamma * (bPrev - $bMin)) AS b0
|
|
56
45
|
|
|
57
|
-
WITH now, r,
|
|
58
|
-
(a0 +
|
|
59
|
-
(b0 +
|
|
46
|
+
WITH now, r, item,
|
|
47
|
+
(a0 + item.w * item.y) AS a1,
|
|
48
|
+
(b0 + item.w * (1.0 - item.y)) AS b1
|
|
60
49
|
|
|
61
50
|
SET r.a = a1,
|
|
62
51
|
r.b = b1,
|
|
@@ -64,7 +53,12 @@ SET r.a = a1,
|
|
|
64
53
|
r.evidence = a1 + b1,
|
|
65
54
|
r.updatedAt = now,
|
|
66
55
|
r.uses = coalesce(r.uses, 0) + 1,
|
|
67
|
-
r.successes = coalesce(r.successes, 0) + CASE WHEN
|
|
68
|
-
r.failures = coalesce(r.failures, 0) + CASE WHEN
|
|
56
|
+
r.successes = coalesce(r.successes, 0) + CASE WHEN item.y >= 0.5 THEN 1 ELSE 0 END,
|
|
57
|
+
r.failures = coalesce(r.failures, 0) + CASE WHEN item.y < 0.5 THEN 1 ELSE 0 END
|
|
69
58
|
|
|
70
|
-
RETURN
|
|
59
|
+
RETURN item.memoryId AS id,
|
|
60
|
+
r.a AS a,
|
|
61
|
+
r.b AS b,
|
|
62
|
+
r.strength AS strength,
|
|
63
|
+
r.evidence AS evidence,
|
|
64
|
+
toString(r.updatedAt) AS updatedAt;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// Parameters:
|
|
2
|
+
// - $ids: Array of memory ids
|
|
3
|
+
WITH [id IN coalesce($ids, []) WHERE id IS NOT NULL AND id <> ""] AS ids
|
|
4
|
+
UNWIND range(0, size(ids) - 1) AS idx
|
|
5
|
+
WITH idx, ids[idx] AS id
|
|
6
|
+
MATCH (m:Memory {id: id})
|
|
7
|
+
OPTIONAL MATCH (m)-[:APPLIES_IN]->(e:EnvironmentFingerprint)
|
|
8
|
+
WITH idx, m, collect(e {
|
|
9
|
+
.hash,
|
|
10
|
+
.os,
|
|
11
|
+
.distro,
|
|
12
|
+
.ci,
|
|
13
|
+
.container,
|
|
14
|
+
.filesystem,
|
|
15
|
+
.workspaceMount,
|
|
16
|
+
.nodeVersion,
|
|
17
|
+
.packageManager,
|
|
18
|
+
.pmVersion
|
|
19
|
+
}) AS envs
|
|
20
|
+
WITH collect({
|
|
21
|
+
idx: idx,
|
|
22
|
+
memory: m {
|
|
23
|
+
.id,
|
|
24
|
+
.kind,
|
|
25
|
+
.polarity,
|
|
26
|
+
.title,
|
|
27
|
+
.content,
|
|
28
|
+
.tags,
|
|
29
|
+
.confidence,
|
|
30
|
+
.utility,
|
|
31
|
+
.triage,
|
|
32
|
+
.antiPattern,
|
|
33
|
+
.createdAt,
|
|
34
|
+
.updatedAt,
|
|
35
|
+
env: envs[0]
|
|
36
|
+
}
|
|
37
|
+
}) AS rows
|
|
38
|
+
UNWIND rows AS row
|
|
39
|
+
WITH row
|
|
40
|
+
ORDER BY row.idx
|
|
41
|
+
RETURN collect(row.memory) AS memories;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
// Parameters:
|
|
2
|
+
// - $agentId: Agent id for RECALLS edges
|
|
3
|
+
// - $memoryIds: Array of memory ids
|
|
4
|
+
// - $includeNodes: Boolean to include node payloads
|
|
5
|
+
WITH
|
|
6
|
+
coalesce($agentId, "") AS agentId,
|
|
7
|
+
[id IN coalesce($memoryIds, []) WHERE id IS NOT NULL AND id <> ""] AS ids,
|
|
8
|
+
coalesce($includeNodes, true) AS includeNodes
|
|
9
|
+
|
|
10
|
+
CALL (ids, includeNodes) {
|
|
11
|
+
WITH ids, includeNodes
|
|
12
|
+
MATCH (m:Memory)
|
|
13
|
+
WHERE includeNodes = true AND m.id IN ids
|
|
14
|
+
OPTIONAL MATCH (m)-[:APPLIES_IN]->(e:EnvironmentFingerprint)
|
|
15
|
+
WITH m, collect(e {
|
|
16
|
+
.hash,
|
|
17
|
+
.os,
|
|
18
|
+
.distro,
|
|
19
|
+
.ci,
|
|
20
|
+
.container,
|
|
21
|
+
.filesystem,
|
|
22
|
+
.workspaceMount,
|
|
23
|
+
.nodeVersion,
|
|
24
|
+
.packageManager,
|
|
25
|
+
.pmVersion
|
|
26
|
+
}) AS envs
|
|
27
|
+
RETURN collect(m {
|
|
28
|
+
.id,
|
|
29
|
+
.kind,
|
|
30
|
+
.polarity,
|
|
31
|
+
.title,
|
|
32
|
+
.content,
|
|
33
|
+
.tags,
|
|
34
|
+
.confidence,
|
|
35
|
+
.utility,
|
|
36
|
+
.triage,
|
|
37
|
+
.antiPattern,
|
|
38
|
+
.createdAt,
|
|
39
|
+
.updatedAt,
|
|
40
|
+
env: envs[0]
|
|
41
|
+
}) AS nodes
|
|
42
|
+
|
|
43
|
+
UNION
|
|
44
|
+
|
|
45
|
+
WITH ids, includeNodes
|
|
46
|
+
WHERE includeNodes = false
|
|
47
|
+
RETURN [] AS nodes
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
CALL (ids, agentId) {
|
|
51
|
+
WITH ids, agentId
|
|
52
|
+
WHERE agentId IS NOT NULL AND agentId <> ""
|
|
53
|
+
MATCH (a:Agent {id: agentId})-[r:RECALLS]->(m:Memory)
|
|
54
|
+
WHERE m.id IN ids
|
|
55
|
+
RETURN collect({
|
|
56
|
+
source: a.id,
|
|
57
|
+
target: m.id,
|
|
58
|
+
kind: "recalls",
|
|
59
|
+
strength: r.strength,
|
|
60
|
+
evidence: r.evidence,
|
|
61
|
+
updatedAt: toString(r.updatedAt)
|
|
62
|
+
}) AS recallEdges
|
|
63
|
+
|
|
64
|
+
UNION
|
|
65
|
+
|
|
66
|
+
WITH ids, agentId
|
|
67
|
+
WHERE agentId IS NULL OR agentId = ""
|
|
68
|
+
RETURN [] AS recallEdges
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
CALL (ids) {
|
|
72
|
+
WITH ids
|
|
73
|
+
MATCH (m1:Memory)-[c:CO_USED_WITH]->(m2:Memory)
|
|
74
|
+
WHERE m1.id IN ids AND m2.id IN ids
|
|
75
|
+
RETURN collect({
|
|
76
|
+
source: m1.id,
|
|
77
|
+
target: m2.id,
|
|
78
|
+
kind: "co_used_with",
|
|
79
|
+
strength: c.strength,
|
|
80
|
+
evidence: c.evidence,
|
|
81
|
+
updatedAt: toString(c.updatedAt)
|
|
82
|
+
}) AS coUsedEdges
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
RETURN nodes, recallEdges + coUsedEdges AS edges;
|
package/dist/cypher/index.ts
CHANGED
|
@@ -20,4 +20,7 @@ export const cypher = {
|
|
|
20
20
|
feedbackCoUsed: loadCypher("feedback_co_used_with_batch.cypher"),
|
|
21
21
|
listMemories: loadCypher("list_memories.cypher"),
|
|
22
22
|
relateConcepts: loadCypher("relate_concepts.cypher"),
|
|
23
|
+
autoRelateByTags: loadCypher("auto_relate_memory_by_tags.cypher"),
|
|
24
|
+
getMemoriesById: loadCypher("get_memories_by_id.cypher"),
|
|
25
|
+
getMemoryGraph: loadCypher("get_memory_graph.cypher"),
|
|
23
26
|
};
|
|
@@ -16,5 +16,8 @@ FOR (m:Memory) ON (m.polarity);
|
|
|
16
16
|
CREATE INDEX memory_kind IF NOT EXISTS
|
|
17
17
|
FOR (m:Memory) ON (m.kind);
|
|
18
18
|
|
|
19
|
+
CREATE INDEX memory_tags IF NOT EXISTS
|
|
20
|
+
FOR (m:Memory) ON (m.tags);
|
|
21
|
+
|
|
19
22
|
CREATE CONSTRAINT agent_id_unique IF NOT EXISTS
|
|
20
23
|
FOR (a:Agent) REQUIRE a.id IS UNIQUE;
|
package/dist/index.cjs
CHANGED
|
@@ -85,7 +85,10 @@ var cypher = {
|
|
|
85
85
|
feedbackBatch: loadCypher("feedback_batch.cypher"),
|
|
86
86
|
feedbackCoUsed: loadCypher("feedback_co_used_with_batch.cypher"),
|
|
87
87
|
listMemories: loadCypher("list_memories.cypher"),
|
|
88
|
-
relateConcepts: loadCypher("relate_concepts.cypher")
|
|
88
|
+
relateConcepts: loadCypher("relate_concepts.cypher"),
|
|
89
|
+
autoRelateByTags: loadCypher("auto_relate_memory_by_tags.cypher"),
|
|
90
|
+
getMemoriesById: loadCypher("get_memories_by_id.cypher"),
|
|
91
|
+
getMemoryGraph: loadCypher("get_memory_graph.cypher")
|
|
89
92
|
};
|
|
90
93
|
|
|
91
94
|
// src/neo4j/schema.ts
|
|
@@ -145,6 +148,31 @@ function envHash(env) {
|
|
|
145
148
|
function clamp01(x) {
|
|
146
149
|
return Math.max(0, Math.min(1, x));
|
|
147
150
|
}
|
|
151
|
+
function parseJsonField(value) {
|
|
152
|
+
if (value === null || value === void 0) return void 0;
|
|
153
|
+
if (typeof value !== "string") return value;
|
|
154
|
+
try {
|
|
155
|
+
return JSON.parse(value);
|
|
156
|
+
} catch {
|
|
157
|
+
return void 0;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
function toDateString(value) {
|
|
161
|
+
if (value === null || value === void 0) return void 0;
|
|
162
|
+
if (typeof value?.toString === "function") return value.toString();
|
|
163
|
+
return String(value);
|
|
164
|
+
}
|
|
165
|
+
var DEFAULT_AUTO_RELATE = {
|
|
166
|
+
enabled: true,
|
|
167
|
+
minSharedTags: 2,
|
|
168
|
+
minWeight: 0.2,
|
|
169
|
+
maxCandidates: 12,
|
|
170
|
+
sameKind: true,
|
|
171
|
+
samePolarity: true,
|
|
172
|
+
allowedKinds: ["semantic", "procedural"]
|
|
173
|
+
};
|
|
174
|
+
var AUTO_RELATE_MIN_SHARED_TAGS = 1;
|
|
175
|
+
var AUTO_RELATE_MIN_MAX_CANDIDATES = 1;
|
|
148
176
|
function toBetaEdge(raw) {
|
|
149
177
|
const aMin = 1e-3;
|
|
150
178
|
const bMin = 1e-3;
|
|
@@ -160,6 +188,23 @@ function toBetaEdge(raw) {
|
|
|
160
188
|
updatedAt: raw?.updatedAt ?? null
|
|
161
189
|
};
|
|
162
190
|
}
|
|
191
|
+
function toMemoryRecord(raw) {
|
|
192
|
+
return {
|
|
193
|
+
id: raw.id,
|
|
194
|
+
kind: raw.kind,
|
|
195
|
+
polarity: raw.polarity ?? "positive",
|
|
196
|
+
title: raw.title,
|
|
197
|
+
content: raw.content,
|
|
198
|
+
tags: raw.tags ?? [],
|
|
199
|
+
confidence: raw.confidence ?? 0.7,
|
|
200
|
+
utility: raw.utility ?? 0.2,
|
|
201
|
+
createdAt: toDateString(raw.createdAt),
|
|
202
|
+
updatedAt: toDateString(raw.updatedAt),
|
|
203
|
+
triage: parseJsonField(raw.triage),
|
|
204
|
+
antiPattern: parseJsonField(raw.antiPattern),
|
|
205
|
+
env: raw.env ?? void 0
|
|
206
|
+
};
|
|
207
|
+
}
|
|
163
208
|
function defaultPolicy(req) {
|
|
164
209
|
return {
|
|
165
210
|
minConfidence: req?.minConfidence ?? 0.65,
|
|
@@ -222,6 +267,7 @@ var MemoryService = class {
|
|
|
222
267
|
fulltextIndex;
|
|
223
268
|
halfLifeSeconds;
|
|
224
269
|
onMemoryEvent;
|
|
270
|
+
autoRelateConfig;
|
|
225
271
|
cyUpsertMemory = cypher.upsertMemory;
|
|
226
272
|
cyUpsertCase = cypher.upsertCase;
|
|
227
273
|
cyRetrieveBundle = cypher.retrieveContextBundle;
|
|
@@ -229,6 +275,9 @@ var MemoryService = class {
|
|
|
229
275
|
cyFeedbackCoUsed = cypher.feedbackCoUsed;
|
|
230
276
|
cyListMemories = cypher.listMemories;
|
|
231
277
|
cyRelateConcepts = cypher.relateConcepts;
|
|
278
|
+
cyAutoRelateByTags = cypher.autoRelateByTags;
|
|
279
|
+
cyGetMemoriesById = cypher.getMemoriesById;
|
|
280
|
+
cyGetMemoryGraph = cypher.getMemoryGraph;
|
|
232
281
|
cyGetRecallEdges = `
|
|
233
282
|
UNWIND $ids AS id
|
|
234
283
|
MATCH (m:Memory {id:id})
|
|
@@ -246,6 +295,22 @@ var MemoryService = class {
|
|
|
246
295
|
this.fulltextIndex = cfg.fulltextIndex ?? "memoryText";
|
|
247
296
|
this.halfLifeSeconds = cfg.halfLifeSeconds ?? 30 * 24 * 3600;
|
|
248
297
|
this.onMemoryEvent = cfg.onMemoryEvent;
|
|
298
|
+
const autoRelate = cfg.autoRelate ?? {};
|
|
299
|
+
this.autoRelateConfig = {
|
|
300
|
+
enabled: autoRelate.enabled ?? DEFAULT_AUTO_RELATE.enabled,
|
|
301
|
+
minSharedTags: Math.max(
|
|
302
|
+
AUTO_RELATE_MIN_SHARED_TAGS,
|
|
303
|
+
Math.floor(autoRelate.minSharedTags ?? DEFAULT_AUTO_RELATE.minSharedTags)
|
|
304
|
+
),
|
|
305
|
+
minWeight: clamp01(autoRelate.minWeight ?? DEFAULT_AUTO_RELATE.minWeight),
|
|
306
|
+
maxCandidates: Math.max(
|
|
307
|
+
AUTO_RELATE_MIN_MAX_CANDIDATES,
|
|
308
|
+
Math.floor(autoRelate.maxCandidates ?? DEFAULT_AUTO_RELATE.maxCandidates)
|
|
309
|
+
),
|
|
310
|
+
sameKind: autoRelate.sameKind ?? DEFAULT_AUTO_RELATE.sameKind,
|
|
311
|
+
samePolarity: autoRelate.samePolarity ?? DEFAULT_AUTO_RELATE.samePolarity,
|
|
312
|
+
allowedKinds: autoRelate.allowedKinds ?? [...DEFAULT_AUTO_RELATE.allowedKinds]
|
|
313
|
+
};
|
|
249
314
|
}
|
|
250
315
|
async init() {
|
|
251
316
|
await ensureSchema(this.client);
|
|
@@ -330,6 +395,21 @@ var MemoryService = class {
|
|
|
330
395
|
}
|
|
331
396
|
);
|
|
332
397
|
}
|
|
398
|
+
const autoRelate = this.autoRelateConfig;
|
|
399
|
+
const allowedKinds = autoRelate.allowedKinds ?? [];
|
|
400
|
+
const canAutoRelate = autoRelate.enabled && tags.length >= autoRelate.minSharedTags && (allowedKinds.length === 0 || allowedKinds.includes(l.kind));
|
|
401
|
+
if (canAutoRelate) {
|
|
402
|
+
await write.run(this.cyAutoRelateByTags, {
|
|
403
|
+
id,
|
|
404
|
+
nowIso: (/* @__PURE__ */ new Date()).toISOString(),
|
|
405
|
+
minSharedTags: autoRelate.minSharedTags,
|
|
406
|
+
minWeight: autoRelate.minWeight,
|
|
407
|
+
maxCandidates: autoRelate.maxCandidates,
|
|
408
|
+
sameKind: autoRelate.sameKind,
|
|
409
|
+
samePolarity: autoRelate.samePolarity,
|
|
410
|
+
allowedKinds
|
|
411
|
+
});
|
|
412
|
+
}
|
|
333
413
|
this.emit({ type: "write", action: "upsertMemory", meta: { id } });
|
|
334
414
|
return { id, deduped: false };
|
|
335
415
|
} finally {
|
|
@@ -483,6 +563,39 @@ ${m.content}`).join("");
|
|
|
483
563
|
await session.close();
|
|
484
564
|
}
|
|
485
565
|
}
|
|
566
|
+
async getMemoriesById(args) {
|
|
567
|
+
const ids = [...new Set((args.ids ?? []).filter(Boolean))];
|
|
568
|
+
if (ids.length === 0) return [];
|
|
569
|
+
const session = this.client.session("READ");
|
|
570
|
+
try {
|
|
571
|
+
const res = await session.run(this.cyGetMemoriesById, { ids });
|
|
572
|
+
const memories = res.records[0]?.get("memories") ?? [];
|
|
573
|
+
return memories.map(toMemoryRecord);
|
|
574
|
+
} finally {
|
|
575
|
+
await session.close();
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
async getMemoryGraph(args) {
|
|
579
|
+
const ids = [...new Set((args.memoryIds ?? []).filter(Boolean))];
|
|
580
|
+
if (ids.length === 0) return { nodes: [], edges: [] };
|
|
581
|
+
const session = this.client.session("READ");
|
|
582
|
+
try {
|
|
583
|
+
const res = await session.run(this.cyGetMemoryGraph, {
|
|
584
|
+
agentId: args.agentId ?? null,
|
|
585
|
+
memoryIds: ids,
|
|
586
|
+
includeNodes: args.includeNodes ?? true
|
|
587
|
+
});
|
|
588
|
+
const record = res.records[0];
|
|
589
|
+
const nodesRaw = record?.get("nodes") ?? [];
|
|
590
|
+
const edges = record?.get("edges") ?? [];
|
|
591
|
+
return {
|
|
592
|
+
nodes: nodesRaw.map(toMemoryRecord),
|
|
593
|
+
edges
|
|
594
|
+
};
|
|
595
|
+
} finally {
|
|
596
|
+
await session.close();
|
|
597
|
+
}
|
|
598
|
+
}
|
|
486
599
|
async listEpisodes(args = {}) {
|
|
487
600
|
return this.listMemories({ ...args, kind: "episodic" });
|
|
488
601
|
}
|
|
@@ -560,11 +673,11 @@ ${m.content}`).join("");
|
|
|
560
673
|
y: yById.get(memoryId) ?? 0,
|
|
561
674
|
w
|
|
562
675
|
}));
|
|
563
|
-
if (items.length === 0) return;
|
|
676
|
+
if (items.length === 0) return { updated: [] };
|
|
564
677
|
const session = this.client.session("WRITE");
|
|
565
678
|
try {
|
|
566
679
|
await session.run("MERGE (a:Agent {id:$id}) RETURN a", { id: fb.agentId });
|
|
567
|
-
await session.run(this.cyFeedbackBatch, {
|
|
680
|
+
const feedbackRes = await session.run(this.cyFeedbackBatch, {
|
|
568
681
|
agentId: fb.agentId,
|
|
569
682
|
nowIso,
|
|
570
683
|
items,
|
|
@@ -572,6 +685,16 @@ ${m.content}`).join("");
|
|
|
572
685
|
aMin: 1e-3,
|
|
573
686
|
bMin: 1e-3
|
|
574
687
|
});
|
|
688
|
+
const updated = feedbackRes.records.map((rec) => {
|
|
689
|
+
const raw = {
|
|
690
|
+
a: rec.get("a"),
|
|
691
|
+
b: rec.get("b"),
|
|
692
|
+
strength: rec.get("strength"),
|
|
693
|
+
evidence: rec.get("evidence"),
|
|
694
|
+
updatedAt: rec.get("updatedAt")
|
|
695
|
+
};
|
|
696
|
+
return { id: rec.get("id"), edge: toBetaEdge(raw) };
|
|
697
|
+
});
|
|
575
698
|
const ids = [...used];
|
|
576
699
|
const pairs = [];
|
|
577
700
|
for (let i = 0; i < ids.length; i++) {
|
|
@@ -593,6 +716,7 @@ ${m.content}`).join("");
|
|
|
593
716
|
});
|
|
594
717
|
}
|
|
595
718
|
this.emit({ type: "write", action: "feedback", meta: { agentId: fb.agentId, usedCount: used.size } });
|
|
719
|
+
return { updated };
|
|
596
720
|
} finally {
|
|
597
721
|
await session.close();
|
|
598
722
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -21,6 +21,13 @@ interface DistilledInvariant {
|
|
|
21
21
|
applicability?: string[];
|
|
22
22
|
risks?: string[];
|
|
23
23
|
}
|
|
24
|
+
interface MemoryTriage {
|
|
25
|
+
symptoms: string[];
|
|
26
|
+
likelyCauses: string[];
|
|
27
|
+
verificationSteps?: string[];
|
|
28
|
+
fixSteps?: string[];
|
|
29
|
+
gotchas?: string[];
|
|
30
|
+
}
|
|
24
31
|
interface MemoryRecord {
|
|
25
32
|
id: string;
|
|
26
33
|
kind: MemoryKind;
|
|
@@ -32,6 +39,7 @@ interface MemoryRecord {
|
|
|
32
39
|
utility: number;
|
|
33
40
|
createdAt?: string;
|
|
34
41
|
updatedAt?: string;
|
|
42
|
+
triage?: MemoryTriage;
|
|
35
43
|
signals?: {
|
|
36
44
|
symptoms?: string[];
|
|
37
45
|
environment?: string[];
|
|
@@ -60,6 +68,18 @@ interface MemorySummary {
|
|
|
60
68
|
createdAt?: string | null;
|
|
61
69
|
updatedAt?: string | null;
|
|
62
70
|
}
|
|
71
|
+
interface MemoryGraphEdge {
|
|
72
|
+
source: string;
|
|
73
|
+
target: string;
|
|
74
|
+
kind: "recalls" | "co_used_with";
|
|
75
|
+
strength: number;
|
|
76
|
+
evidence: number;
|
|
77
|
+
updatedAt?: string | null;
|
|
78
|
+
}
|
|
79
|
+
interface MemoryGraphResponse {
|
|
80
|
+
nodes: MemoryRecord[];
|
|
81
|
+
edges: MemoryGraphEdge[];
|
|
82
|
+
}
|
|
63
83
|
interface CaseRecord {
|
|
64
84
|
id: string;
|
|
65
85
|
title: string;
|
|
@@ -92,6 +112,14 @@ interface ListMemoriesArgs {
|
|
|
92
112
|
limit?: number;
|
|
93
113
|
agentId?: string;
|
|
94
114
|
}
|
|
115
|
+
interface GetMemoriesByIdArgs {
|
|
116
|
+
ids: string[];
|
|
117
|
+
}
|
|
118
|
+
interface GetMemoryGraphArgs {
|
|
119
|
+
agentId?: string;
|
|
120
|
+
memoryIds: string[];
|
|
121
|
+
includeNodes?: boolean;
|
|
122
|
+
}
|
|
95
123
|
interface BetaEdge {
|
|
96
124
|
a: number;
|
|
97
125
|
b: number;
|
|
@@ -136,6 +164,12 @@ interface MemoryFeedback {
|
|
|
136
164
|
metrics?: FeedbackMetrics;
|
|
137
165
|
notes?: string;
|
|
138
166
|
}
|
|
167
|
+
interface MemoryFeedbackResult {
|
|
168
|
+
updated: Array<{
|
|
169
|
+
id: string;
|
|
170
|
+
edge: BetaEdge;
|
|
171
|
+
}>;
|
|
172
|
+
}
|
|
139
173
|
interface CaptureEpisodeArgs {
|
|
140
174
|
agentId: string;
|
|
141
175
|
runId: string;
|
|
@@ -163,13 +197,7 @@ interface LearningCandidate {
|
|
|
163
197
|
confidence: number;
|
|
164
198
|
signals?: MemoryRecord["signals"];
|
|
165
199
|
env?: EnvironmentFingerprint;
|
|
166
|
-
triage?:
|
|
167
|
-
symptoms: string[];
|
|
168
|
-
likelyCauses: string[];
|
|
169
|
-
verificationSteps?: string[];
|
|
170
|
-
fixSteps?: string[];
|
|
171
|
-
gotchas?: string[];
|
|
172
|
-
};
|
|
200
|
+
triage?: MemoryTriage;
|
|
173
201
|
antiPattern?: MemoryRecord["antiPattern"];
|
|
174
202
|
}
|
|
175
203
|
interface SaveLearningRequest {
|
|
@@ -195,6 +223,15 @@ interface SaveLearningResult {
|
|
|
195
223
|
reason: string;
|
|
196
224
|
}>;
|
|
197
225
|
}
|
|
226
|
+
interface AutoRelateConfig {
|
|
227
|
+
enabled?: boolean;
|
|
228
|
+
minSharedTags?: number;
|
|
229
|
+
minWeight?: number;
|
|
230
|
+
maxCandidates?: number;
|
|
231
|
+
sameKind?: boolean;
|
|
232
|
+
samePolarity?: boolean;
|
|
233
|
+
allowedKinds?: MemoryKind[];
|
|
234
|
+
}
|
|
198
235
|
interface MemoryServiceConfig {
|
|
199
236
|
neo4j: {
|
|
200
237
|
uri: string;
|
|
@@ -205,6 +242,7 @@ interface MemoryServiceConfig {
|
|
|
205
242
|
vectorIndex?: string;
|
|
206
243
|
fulltextIndex?: string;
|
|
207
244
|
halfLifeSeconds?: number;
|
|
245
|
+
autoRelate?: AutoRelateConfig;
|
|
208
246
|
onMemoryEvent?: (event: MemoryEvent) => void;
|
|
209
247
|
}
|
|
210
248
|
type MemoryToolName = "store_skill" | "store_pattern" | "store_concept" | "relate_concepts" | "recall_skills" | "recall_concepts" | "recall_patterns";
|
|
@@ -222,6 +260,7 @@ declare class MemoryService {
|
|
|
222
260
|
private fulltextIndex;
|
|
223
261
|
private halfLifeSeconds;
|
|
224
262
|
private onMemoryEvent?;
|
|
263
|
+
private autoRelateConfig;
|
|
225
264
|
private cyUpsertMemory;
|
|
226
265
|
private cyUpsertCase;
|
|
227
266
|
private cyRetrieveBundle;
|
|
@@ -229,6 +268,9 @@ declare class MemoryService {
|
|
|
229
268
|
private cyFeedbackCoUsed;
|
|
230
269
|
private cyListMemories;
|
|
231
270
|
private cyRelateConcepts;
|
|
271
|
+
private cyAutoRelateByTags;
|
|
272
|
+
private cyGetMemoriesById;
|
|
273
|
+
private cyGetMemoryGraph;
|
|
232
274
|
private cyGetRecallEdges;
|
|
233
275
|
constructor(cfg: MemoryServiceConfig);
|
|
234
276
|
init(): Promise<void>;
|
|
@@ -255,6 +297,8 @@ declare class MemoryService {
|
|
|
255
297
|
*/
|
|
256
298
|
retrieveContextBundle(args: RetrieveContextArgs): Promise<ContextBundle>;
|
|
257
299
|
listMemories(args?: ListMemoriesArgs): Promise<MemorySummary[]>;
|
|
300
|
+
getMemoriesById(args: GetMemoriesByIdArgs): Promise<MemoryRecord[]>;
|
|
301
|
+
getMemoryGraph(args: GetMemoryGraphArgs): Promise<MemoryGraphResponse>;
|
|
258
302
|
listEpisodes(args?: Omit<ListMemoriesArgs, "kind">): Promise<MemorySummary[]>;
|
|
259
303
|
listSkills(args?: Omit<ListMemoriesArgs, "kind">): Promise<MemorySummary[]>;
|
|
260
304
|
listConcepts(args?: Omit<ListMemoriesArgs, "kind">): Promise<MemorySummary[]>;
|
|
@@ -269,7 +313,7 @@ declare class MemoryService {
|
|
|
269
313
|
* Reinforce/degrade agent->memory association weights using a single batched Cypher query.
|
|
270
314
|
* This supports mid-run retrieval by making feedback cheap and frequent.
|
|
271
315
|
*/
|
|
272
|
-
feedback(fb: MemoryFeedback): Promise<
|
|
316
|
+
feedback(fb: MemoryFeedback): Promise<MemoryFeedbackResult>;
|
|
273
317
|
/**
|
|
274
318
|
* Save distilled learnings discovered during a task.
|
|
275
319
|
* Enforces quality gates and stores negative memories explicitly.
|
|
@@ -307,6 +351,9 @@ declare const cypher: {
|
|
|
307
351
|
feedbackCoUsed: string;
|
|
308
352
|
listMemories: string;
|
|
309
353
|
relateConcepts: string;
|
|
354
|
+
autoRelateByTags: string;
|
|
355
|
+
getMemoriesById: string;
|
|
356
|
+
getMemoryGraph: string;
|
|
310
357
|
};
|
|
311
358
|
|
|
312
359
|
declare function createMemoryTools(service: MemoryService): MemoryToolSet;
|
|
@@ -317,4 +364,4 @@ declare function newId(prefix: string): string;
|
|
|
317
364
|
declare function normaliseSymptom(s: string): string;
|
|
318
365
|
declare function envHash(env: EnvironmentFingerprint): string;
|
|
319
366
|
|
|
320
|
-
export { type BetaEdge, type CaptureEpisodeArgs, type CaptureStepEpisodeArgs, type CaseRecord, type ContextBundle, type ContextMemoryBase, type ContextMemorySummary, type DistilledInvariant, type EnvironmentFingerprint, type FeedbackMetrics, type LearningCandidate, type ListMemoriesArgs, type MemoryEvent, type MemoryFeedback, type MemoryKind, type MemoryPolarity, type MemoryRecord, MemoryService, type MemoryServiceConfig, type MemorySummary, type MemoryToolDefinition, type MemoryToolName, type MemoryToolSet, Neo4jClient, type Neo4jClientConfig, type RetrieveContextArgs, type SaveLearningRequest, type SaveLearningResult, canonicaliseForHash, createMemoryService, createMemoryTools, cypher, ensureSchema, envHash, loadCypher, migrate, newId, normaliseSymptom, schemaVersion, sha256Hex };
|
|
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 };
|
package/dist/index.js
CHANGED
|
@@ -35,7 +35,10 @@ var cypher = {
|
|
|
35
35
|
feedbackBatch: loadCypher("feedback_batch.cypher"),
|
|
36
36
|
feedbackCoUsed: loadCypher("feedback_co_used_with_batch.cypher"),
|
|
37
37
|
listMemories: loadCypher("list_memories.cypher"),
|
|
38
|
-
relateConcepts: loadCypher("relate_concepts.cypher")
|
|
38
|
+
relateConcepts: loadCypher("relate_concepts.cypher"),
|
|
39
|
+
autoRelateByTags: loadCypher("auto_relate_memory_by_tags.cypher"),
|
|
40
|
+
getMemoriesById: loadCypher("get_memories_by_id.cypher"),
|
|
41
|
+
getMemoryGraph: loadCypher("get_memory_graph.cypher")
|
|
39
42
|
};
|
|
40
43
|
|
|
41
44
|
// src/neo4j/schema.ts
|
|
@@ -95,6 +98,31 @@ function envHash(env) {
|
|
|
95
98
|
function clamp01(x) {
|
|
96
99
|
return Math.max(0, Math.min(1, x));
|
|
97
100
|
}
|
|
101
|
+
function parseJsonField(value) {
|
|
102
|
+
if (value === null || value === void 0) return void 0;
|
|
103
|
+
if (typeof value !== "string") return value;
|
|
104
|
+
try {
|
|
105
|
+
return JSON.parse(value);
|
|
106
|
+
} catch {
|
|
107
|
+
return void 0;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
function toDateString(value) {
|
|
111
|
+
if (value === null || value === void 0) return void 0;
|
|
112
|
+
if (typeof value?.toString === "function") return value.toString();
|
|
113
|
+
return String(value);
|
|
114
|
+
}
|
|
115
|
+
var DEFAULT_AUTO_RELATE = {
|
|
116
|
+
enabled: true,
|
|
117
|
+
minSharedTags: 2,
|
|
118
|
+
minWeight: 0.2,
|
|
119
|
+
maxCandidates: 12,
|
|
120
|
+
sameKind: true,
|
|
121
|
+
samePolarity: true,
|
|
122
|
+
allowedKinds: ["semantic", "procedural"]
|
|
123
|
+
};
|
|
124
|
+
var AUTO_RELATE_MIN_SHARED_TAGS = 1;
|
|
125
|
+
var AUTO_RELATE_MIN_MAX_CANDIDATES = 1;
|
|
98
126
|
function toBetaEdge(raw) {
|
|
99
127
|
const aMin = 1e-3;
|
|
100
128
|
const bMin = 1e-3;
|
|
@@ -110,6 +138,23 @@ function toBetaEdge(raw) {
|
|
|
110
138
|
updatedAt: raw?.updatedAt ?? null
|
|
111
139
|
};
|
|
112
140
|
}
|
|
141
|
+
function toMemoryRecord(raw) {
|
|
142
|
+
return {
|
|
143
|
+
id: raw.id,
|
|
144
|
+
kind: raw.kind,
|
|
145
|
+
polarity: raw.polarity ?? "positive",
|
|
146
|
+
title: raw.title,
|
|
147
|
+
content: raw.content,
|
|
148
|
+
tags: raw.tags ?? [],
|
|
149
|
+
confidence: raw.confidence ?? 0.7,
|
|
150
|
+
utility: raw.utility ?? 0.2,
|
|
151
|
+
createdAt: toDateString(raw.createdAt),
|
|
152
|
+
updatedAt: toDateString(raw.updatedAt),
|
|
153
|
+
triage: parseJsonField(raw.triage),
|
|
154
|
+
antiPattern: parseJsonField(raw.antiPattern),
|
|
155
|
+
env: raw.env ?? void 0
|
|
156
|
+
};
|
|
157
|
+
}
|
|
113
158
|
function defaultPolicy(req) {
|
|
114
159
|
return {
|
|
115
160
|
minConfidence: req?.minConfidence ?? 0.65,
|
|
@@ -172,6 +217,7 @@ var MemoryService = class {
|
|
|
172
217
|
fulltextIndex;
|
|
173
218
|
halfLifeSeconds;
|
|
174
219
|
onMemoryEvent;
|
|
220
|
+
autoRelateConfig;
|
|
175
221
|
cyUpsertMemory = cypher.upsertMemory;
|
|
176
222
|
cyUpsertCase = cypher.upsertCase;
|
|
177
223
|
cyRetrieveBundle = cypher.retrieveContextBundle;
|
|
@@ -179,6 +225,9 @@ var MemoryService = class {
|
|
|
179
225
|
cyFeedbackCoUsed = cypher.feedbackCoUsed;
|
|
180
226
|
cyListMemories = cypher.listMemories;
|
|
181
227
|
cyRelateConcepts = cypher.relateConcepts;
|
|
228
|
+
cyAutoRelateByTags = cypher.autoRelateByTags;
|
|
229
|
+
cyGetMemoriesById = cypher.getMemoriesById;
|
|
230
|
+
cyGetMemoryGraph = cypher.getMemoryGraph;
|
|
182
231
|
cyGetRecallEdges = `
|
|
183
232
|
UNWIND $ids AS id
|
|
184
233
|
MATCH (m:Memory {id:id})
|
|
@@ -196,6 +245,22 @@ var MemoryService = class {
|
|
|
196
245
|
this.fulltextIndex = cfg.fulltextIndex ?? "memoryText";
|
|
197
246
|
this.halfLifeSeconds = cfg.halfLifeSeconds ?? 30 * 24 * 3600;
|
|
198
247
|
this.onMemoryEvent = cfg.onMemoryEvent;
|
|
248
|
+
const autoRelate = cfg.autoRelate ?? {};
|
|
249
|
+
this.autoRelateConfig = {
|
|
250
|
+
enabled: autoRelate.enabled ?? DEFAULT_AUTO_RELATE.enabled,
|
|
251
|
+
minSharedTags: Math.max(
|
|
252
|
+
AUTO_RELATE_MIN_SHARED_TAGS,
|
|
253
|
+
Math.floor(autoRelate.minSharedTags ?? DEFAULT_AUTO_RELATE.minSharedTags)
|
|
254
|
+
),
|
|
255
|
+
minWeight: clamp01(autoRelate.minWeight ?? DEFAULT_AUTO_RELATE.minWeight),
|
|
256
|
+
maxCandidates: Math.max(
|
|
257
|
+
AUTO_RELATE_MIN_MAX_CANDIDATES,
|
|
258
|
+
Math.floor(autoRelate.maxCandidates ?? DEFAULT_AUTO_RELATE.maxCandidates)
|
|
259
|
+
),
|
|
260
|
+
sameKind: autoRelate.sameKind ?? DEFAULT_AUTO_RELATE.sameKind,
|
|
261
|
+
samePolarity: autoRelate.samePolarity ?? DEFAULT_AUTO_RELATE.samePolarity,
|
|
262
|
+
allowedKinds: autoRelate.allowedKinds ?? [...DEFAULT_AUTO_RELATE.allowedKinds]
|
|
263
|
+
};
|
|
199
264
|
}
|
|
200
265
|
async init() {
|
|
201
266
|
await ensureSchema(this.client);
|
|
@@ -280,6 +345,21 @@ var MemoryService = class {
|
|
|
280
345
|
}
|
|
281
346
|
);
|
|
282
347
|
}
|
|
348
|
+
const autoRelate = this.autoRelateConfig;
|
|
349
|
+
const allowedKinds = autoRelate.allowedKinds ?? [];
|
|
350
|
+
const canAutoRelate = autoRelate.enabled && tags.length >= autoRelate.minSharedTags && (allowedKinds.length === 0 || allowedKinds.includes(l.kind));
|
|
351
|
+
if (canAutoRelate) {
|
|
352
|
+
await write.run(this.cyAutoRelateByTags, {
|
|
353
|
+
id,
|
|
354
|
+
nowIso: (/* @__PURE__ */ new Date()).toISOString(),
|
|
355
|
+
minSharedTags: autoRelate.minSharedTags,
|
|
356
|
+
minWeight: autoRelate.minWeight,
|
|
357
|
+
maxCandidates: autoRelate.maxCandidates,
|
|
358
|
+
sameKind: autoRelate.sameKind,
|
|
359
|
+
samePolarity: autoRelate.samePolarity,
|
|
360
|
+
allowedKinds
|
|
361
|
+
});
|
|
362
|
+
}
|
|
283
363
|
this.emit({ type: "write", action: "upsertMemory", meta: { id } });
|
|
284
364
|
return { id, deduped: false };
|
|
285
365
|
} finally {
|
|
@@ -433,6 +513,39 @@ ${m.content}`).join("");
|
|
|
433
513
|
await session.close();
|
|
434
514
|
}
|
|
435
515
|
}
|
|
516
|
+
async getMemoriesById(args) {
|
|
517
|
+
const ids = [...new Set((args.ids ?? []).filter(Boolean))];
|
|
518
|
+
if (ids.length === 0) return [];
|
|
519
|
+
const session = this.client.session("READ");
|
|
520
|
+
try {
|
|
521
|
+
const res = await session.run(this.cyGetMemoriesById, { ids });
|
|
522
|
+
const memories = res.records[0]?.get("memories") ?? [];
|
|
523
|
+
return memories.map(toMemoryRecord);
|
|
524
|
+
} finally {
|
|
525
|
+
await session.close();
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
async getMemoryGraph(args) {
|
|
529
|
+
const ids = [...new Set((args.memoryIds ?? []).filter(Boolean))];
|
|
530
|
+
if (ids.length === 0) return { nodes: [], edges: [] };
|
|
531
|
+
const session = this.client.session("READ");
|
|
532
|
+
try {
|
|
533
|
+
const res = await session.run(this.cyGetMemoryGraph, {
|
|
534
|
+
agentId: args.agentId ?? null,
|
|
535
|
+
memoryIds: ids,
|
|
536
|
+
includeNodes: args.includeNodes ?? true
|
|
537
|
+
});
|
|
538
|
+
const record = res.records[0];
|
|
539
|
+
const nodesRaw = record?.get("nodes") ?? [];
|
|
540
|
+
const edges = record?.get("edges") ?? [];
|
|
541
|
+
return {
|
|
542
|
+
nodes: nodesRaw.map(toMemoryRecord),
|
|
543
|
+
edges
|
|
544
|
+
};
|
|
545
|
+
} finally {
|
|
546
|
+
await session.close();
|
|
547
|
+
}
|
|
548
|
+
}
|
|
436
549
|
async listEpisodes(args = {}) {
|
|
437
550
|
return this.listMemories({ ...args, kind: "episodic" });
|
|
438
551
|
}
|
|
@@ -510,11 +623,11 @@ ${m.content}`).join("");
|
|
|
510
623
|
y: yById.get(memoryId) ?? 0,
|
|
511
624
|
w
|
|
512
625
|
}));
|
|
513
|
-
if (items.length === 0) return;
|
|
626
|
+
if (items.length === 0) return { updated: [] };
|
|
514
627
|
const session = this.client.session("WRITE");
|
|
515
628
|
try {
|
|
516
629
|
await session.run("MERGE (a:Agent {id:$id}) RETURN a", { id: fb.agentId });
|
|
517
|
-
await session.run(this.cyFeedbackBatch, {
|
|
630
|
+
const feedbackRes = await session.run(this.cyFeedbackBatch, {
|
|
518
631
|
agentId: fb.agentId,
|
|
519
632
|
nowIso,
|
|
520
633
|
items,
|
|
@@ -522,6 +635,16 @@ ${m.content}`).join("");
|
|
|
522
635
|
aMin: 1e-3,
|
|
523
636
|
bMin: 1e-3
|
|
524
637
|
});
|
|
638
|
+
const updated = feedbackRes.records.map((rec) => {
|
|
639
|
+
const raw = {
|
|
640
|
+
a: rec.get("a"),
|
|
641
|
+
b: rec.get("b"),
|
|
642
|
+
strength: rec.get("strength"),
|
|
643
|
+
evidence: rec.get("evidence"),
|
|
644
|
+
updatedAt: rec.get("updatedAt")
|
|
645
|
+
};
|
|
646
|
+
return { id: rec.get("id"), edge: toBetaEdge(raw) };
|
|
647
|
+
});
|
|
525
648
|
const ids = [...used];
|
|
526
649
|
const pairs = [];
|
|
527
650
|
for (let i = 0; i < ids.length; i++) {
|
|
@@ -543,6 +666,7 @@ ${m.content}`).join("");
|
|
|
543
666
|
});
|
|
544
667
|
}
|
|
545
668
|
this.emit({ type: "write", action: "feedback", meta: { agentId: fb.agentId, usedCount: used.size } });
|
|
669
|
+
return { updated };
|
|
546
670
|
} finally {
|
|
547
671
|
await session.close();
|
|
548
672
|
}
|