neo4j-agent-memory 0.3.19 → 0.4.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 +29 -1
- package/dist/cypher/auto_relate_memory_by_tags.cypher +45 -0
- package/dist/cypher/index.ts +1 -0
- package/dist/cypher/schema.cypher +3 -0
- package/dist/index.cjs +46 -1
- package/dist/index.d.cts +246 -0
- package/dist/index.d.ts +14 -1
- package/dist/index.js +46 -1
- 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({
|
|
@@ -61,6 +70,25 @@ Notes:
|
|
|
61
70
|
- `createMemoryService` runs schema setup on init.
|
|
62
71
|
- Cypher assets are bundled at `dist/cypher` in the published package.
|
|
63
72
|
|
|
73
|
+
Auto-relate config (defaults):
|
|
74
|
+
- `enabled: true`
|
|
75
|
+
- `minSharedTags: 2`
|
|
76
|
+
- `minWeight: 0.2`
|
|
77
|
+
- `maxCandidates: 12`
|
|
78
|
+
- `sameKind: true`
|
|
79
|
+
- `samePolarity: true`
|
|
80
|
+
- `allowedKinds: ["semantic", "procedural"]`
|
|
81
|
+
|
|
82
|
+
Auto-relate behavior:
|
|
83
|
+
- Uses tag overlap with Jaccard weight (`shared / (a + b - shared)`).
|
|
84
|
+
- Runs only for newly inserted memories (skips deduped).
|
|
85
|
+
- Applies filters for `sameKind`, `samePolarity`, and `allowedKinds`.
|
|
86
|
+
- Requires `minSharedTags` and `minWeight` to pass before linking.
|
|
87
|
+
- Limits to `maxCandidates` highest-weight neighbors.
|
|
88
|
+
|
|
89
|
+
Performance note:
|
|
90
|
+
- Auto-relate scans candidate memories with tag filtering; for large graphs, keep tags selective and consider tightening `maxCandidates` and `minSharedTags`.
|
|
91
|
+
|
|
64
92
|
## Tool adapter (createMemoryTools)
|
|
65
93
|
|
|
66
94
|
Use the tool factory to preserve the existing tool surface used by the demo:
|
|
@@ -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;
|
package/dist/cypher/index.ts
CHANGED
|
@@ -20,4 +20,5 @@ 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"),
|
|
23
24
|
};
|
|
@@ -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,8 @@ 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")
|
|
89
90
|
};
|
|
90
91
|
|
|
91
92
|
// src/neo4j/schema.ts
|
|
@@ -145,6 +146,17 @@ function envHash(env) {
|
|
|
145
146
|
function clamp01(x) {
|
|
146
147
|
return Math.max(0, Math.min(1, x));
|
|
147
148
|
}
|
|
149
|
+
var DEFAULT_AUTO_RELATE = {
|
|
150
|
+
enabled: true,
|
|
151
|
+
minSharedTags: 2,
|
|
152
|
+
minWeight: 0.2,
|
|
153
|
+
maxCandidates: 12,
|
|
154
|
+
sameKind: true,
|
|
155
|
+
samePolarity: true,
|
|
156
|
+
allowedKinds: ["semantic", "procedural"]
|
|
157
|
+
};
|
|
158
|
+
var AUTO_RELATE_MIN_SHARED_TAGS = 1;
|
|
159
|
+
var AUTO_RELATE_MIN_MAX_CANDIDATES = 1;
|
|
148
160
|
function toBetaEdge(raw) {
|
|
149
161
|
const aMin = 1e-3;
|
|
150
162
|
const bMin = 1e-3;
|
|
@@ -222,6 +234,7 @@ var MemoryService = class {
|
|
|
222
234
|
fulltextIndex;
|
|
223
235
|
halfLifeSeconds;
|
|
224
236
|
onMemoryEvent;
|
|
237
|
+
autoRelateConfig;
|
|
225
238
|
cyUpsertMemory = cypher.upsertMemory;
|
|
226
239
|
cyUpsertCase = cypher.upsertCase;
|
|
227
240
|
cyRetrieveBundle = cypher.retrieveContextBundle;
|
|
@@ -229,6 +242,7 @@ var MemoryService = class {
|
|
|
229
242
|
cyFeedbackCoUsed = cypher.feedbackCoUsed;
|
|
230
243
|
cyListMemories = cypher.listMemories;
|
|
231
244
|
cyRelateConcepts = cypher.relateConcepts;
|
|
245
|
+
cyAutoRelateByTags = cypher.autoRelateByTags;
|
|
232
246
|
cyGetRecallEdges = `
|
|
233
247
|
UNWIND $ids AS id
|
|
234
248
|
MATCH (m:Memory {id:id})
|
|
@@ -246,6 +260,22 @@ var MemoryService = class {
|
|
|
246
260
|
this.fulltextIndex = cfg.fulltextIndex ?? "memoryText";
|
|
247
261
|
this.halfLifeSeconds = cfg.halfLifeSeconds ?? 30 * 24 * 3600;
|
|
248
262
|
this.onMemoryEvent = cfg.onMemoryEvent;
|
|
263
|
+
const autoRelate = cfg.autoRelate ?? {};
|
|
264
|
+
this.autoRelateConfig = {
|
|
265
|
+
enabled: autoRelate.enabled ?? DEFAULT_AUTO_RELATE.enabled,
|
|
266
|
+
minSharedTags: Math.max(
|
|
267
|
+
AUTO_RELATE_MIN_SHARED_TAGS,
|
|
268
|
+
Math.floor(autoRelate.minSharedTags ?? DEFAULT_AUTO_RELATE.minSharedTags)
|
|
269
|
+
),
|
|
270
|
+
minWeight: clamp01(autoRelate.minWeight ?? DEFAULT_AUTO_RELATE.minWeight),
|
|
271
|
+
maxCandidates: Math.max(
|
|
272
|
+
AUTO_RELATE_MIN_MAX_CANDIDATES,
|
|
273
|
+
Math.floor(autoRelate.maxCandidates ?? DEFAULT_AUTO_RELATE.maxCandidates)
|
|
274
|
+
),
|
|
275
|
+
sameKind: autoRelate.sameKind ?? DEFAULT_AUTO_RELATE.sameKind,
|
|
276
|
+
samePolarity: autoRelate.samePolarity ?? DEFAULT_AUTO_RELATE.samePolarity,
|
|
277
|
+
allowedKinds: autoRelate.allowedKinds ?? [...DEFAULT_AUTO_RELATE.allowedKinds]
|
|
278
|
+
};
|
|
249
279
|
}
|
|
250
280
|
async init() {
|
|
251
281
|
await ensureSchema(this.client);
|
|
@@ -330,6 +360,21 @@ var MemoryService = class {
|
|
|
330
360
|
}
|
|
331
361
|
);
|
|
332
362
|
}
|
|
363
|
+
const autoRelate = this.autoRelateConfig;
|
|
364
|
+
const allowedKinds = autoRelate.allowedKinds ?? [];
|
|
365
|
+
const canAutoRelate = autoRelate.enabled && tags.length >= autoRelate.minSharedTags && (allowedKinds.length === 0 || allowedKinds.includes(l.kind));
|
|
366
|
+
if (canAutoRelate) {
|
|
367
|
+
await write.run(this.cyAutoRelateByTags, {
|
|
368
|
+
id,
|
|
369
|
+
nowIso: (/* @__PURE__ */ new Date()).toISOString(),
|
|
370
|
+
minSharedTags: autoRelate.minSharedTags,
|
|
371
|
+
minWeight: autoRelate.minWeight,
|
|
372
|
+
maxCandidates: autoRelate.maxCandidates,
|
|
373
|
+
sameKind: autoRelate.sameKind,
|
|
374
|
+
samePolarity: autoRelate.samePolarity,
|
|
375
|
+
allowedKinds
|
|
376
|
+
});
|
|
377
|
+
}
|
|
333
378
|
this.emit({ type: "write", action: "upsertMemory", meta: { id } });
|
|
334
379
|
return { id, deduped: false };
|
|
335
380
|
} finally {
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
import { Session } from 'neo4j-driver';
|
|
2
|
+
|
|
3
|
+
type MemoryKind = "semantic" | "procedural" | "episodic";
|
|
4
|
+
type MemoryPolarity = "positive" | "negative";
|
|
5
|
+
interface EnvironmentFingerprint {
|
|
6
|
+
hash?: string;
|
|
7
|
+
os?: "macos" | "linux" | "windows";
|
|
8
|
+
distro?: string;
|
|
9
|
+
ci?: string;
|
|
10
|
+
container?: boolean;
|
|
11
|
+
filesystem?: string;
|
|
12
|
+
workspaceMount?: "local" | "network" | "bind" | "readonly";
|
|
13
|
+
nodeVersion?: string;
|
|
14
|
+
packageManager?: "npm" | "pnpm" | "yarn";
|
|
15
|
+
pmVersion?: string;
|
|
16
|
+
}
|
|
17
|
+
interface DistilledInvariant {
|
|
18
|
+
invariant: string;
|
|
19
|
+
justification?: string;
|
|
20
|
+
verification?: string[];
|
|
21
|
+
applicability?: string[];
|
|
22
|
+
risks?: string[];
|
|
23
|
+
}
|
|
24
|
+
interface MemoryRecord {
|
|
25
|
+
id: string;
|
|
26
|
+
kind: MemoryKind;
|
|
27
|
+
polarity: MemoryPolarity;
|
|
28
|
+
title: string;
|
|
29
|
+
content: string;
|
|
30
|
+
tags: string[];
|
|
31
|
+
confidence: number;
|
|
32
|
+
utility: number;
|
|
33
|
+
createdAt?: string;
|
|
34
|
+
updatedAt?: string;
|
|
35
|
+
signals?: {
|
|
36
|
+
symptoms?: string[];
|
|
37
|
+
environment?: string[];
|
|
38
|
+
};
|
|
39
|
+
distilled?: {
|
|
40
|
+
invariants?: DistilledInvariant[];
|
|
41
|
+
steps?: string[];
|
|
42
|
+
verificationSteps?: string[];
|
|
43
|
+
gotchas?: string[];
|
|
44
|
+
};
|
|
45
|
+
antiPattern?: {
|
|
46
|
+
action: string;
|
|
47
|
+
whyBad: string;
|
|
48
|
+
saferAlternative?: string;
|
|
49
|
+
};
|
|
50
|
+
env?: EnvironmentFingerprint;
|
|
51
|
+
}
|
|
52
|
+
interface CaseRecord {
|
|
53
|
+
id: string;
|
|
54
|
+
title: string;
|
|
55
|
+
summary: string;
|
|
56
|
+
outcome: "resolved" | "unresolved" | "workaround";
|
|
57
|
+
symptoms: string[];
|
|
58
|
+
env: EnvironmentFingerprint;
|
|
59
|
+
resolvedByMemoryIds: string[];
|
|
60
|
+
negativeMemoryIds: string[];
|
|
61
|
+
resolvedAtIso?: string | null;
|
|
62
|
+
}
|
|
63
|
+
interface RetrieveContextArgs {
|
|
64
|
+
agentId: string;
|
|
65
|
+
prompt: string;
|
|
66
|
+
symptoms?: string[];
|
|
67
|
+
tags?: string[];
|
|
68
|
+
kinds?: MemoryKind[];
|
|
69
|
+
env?: EnvironmentFingerprint;
|
|
70
|
+
baseline?: Record<string, {
|
|
71
|
+
a: number;
|
|
72
|
+
b: number;
|
|
73
|
+
}>;
|
|
74
|
+
caseLimit?: number;
|
|
75
|
+
fixLimit?: number;
|
|
76
|
+
dontLimit?: number;
|
|
77
|
+
nowIso?: string;
|
|
78
|
+
}
|
|
79
|
+
interface BetaEdge {
|
|
80
|
+
a: number;
|
|
81
|
+
b: number;
|
|
82
|
+
/** Posterior mean a/(a+b), cached for query speed */
|
|
83
|
+
strength: number;
|
|
84
|
+
/** Evidence mass a+b, cached for query speed */
|
|
85
|
+
evidence: number;
|
|
86
|
+
updatedAt: string | null;
|
|
87
|
+
}
|
|
88
|
+
type ContextMemoryBase = Pick<MemoryRecord, "id" | "kind" | "polarity" | "title" | "content" | "tags" | "confidence" | "utility" | "updatedAt">;
|
|
89
|
+
type ContextMemorySummary = ContextMemoryBase & {
|
|
90
|
+
/** Posterior snapshot before the current run (baseline) */
|
|
91
|
+
edgeBefore?: BetaEdge;
|
|
92
|
+
/** Posterior snapshot after the current run (undefined until feedback arrives) */
|
|
93
|
+
edgeAfter?: BetaEdge;
|
|
94
|
+
};
|
|
95
|
+
interface ContextBundle {
|
|
96
|
+
sessionId: string;
|
|
97
|
+
sections: {
|
|
98
|
+
fix: ContextMemorySummary[];
|
|
99
|
+
doNotDo: ContextMemorySummary[];
|
|
100
|
+
};
|
|
101
|
+
injection: {
|
|
102
|
+
fixBlock: string;
|
|
103
|
+
doNotDoBlock: string;
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
interface FeedbackMetrics {
|
|
107
|
+
durationMs?: number;
|
|
108
|
+
quality?: number;
|
|
109
|
+
hallucinationRisk?: number;
|
|
110
|
+
toolCalls?: number;
|
|
111
|
+
verificationPassed?: boolean;
|
|
112
|
+
}
|
|
113
|
+
interface MemoryFeedback {
|
|
114
|
+
agentId: string;
|
|
115
|
+
sessionId: string;
|
|
116
|
+
usedIds: string[];
|
|
117
|
+
usefulIds: string[];
|
|
118
|
+
notUsefulIds: string[];
|
|
119
|
+
preventedErrorIds?: string[];
|
|
120
|
+
metrics?: FeedbackMetrics;
|
|
121
|
+
notes?: string;
|
|
122
|
+
}
|
|
123
|
+
interface LearningCandidate {
|
|
124
|
+
kind: MemoryKind;
|
|
125
|
+
polarity?: MemoryPolarity;
|
|
126
|
+
title: string;
|
|
127
|
+
content: string;
|
|
128
|
+
tags: string[];
|
|
129
|
+
confidence: number;
|
|
130
|
+
signals?: MemoryRecord["signals"];
|
|
131
|
+
env?: EnvironmentFingerprint;
|
|
132
|
+
triage?: {
|
|
133
|
+
symptoms: string[];
|
|
134
|
+
likelyCauses: string[];
|
|
135
|
+
verificationSteps?: string[];
|
|
136
|
+
fixSteps?: string[];
|
|
137
|
+
gotchas?: string[];
|
|
138
|
+
};
|
|
139
|
+
antiPattern?: MemoryRecord["antiPattern"];
|
|
140
|
+
}
|
|
141
|
+
interface SaveLearningRequest {
|
|
142
|
+
agentId: string;
|
|
143
|
+
sessionId?: string;
|
|
144
|
+
taskId?: string;
|
|
145
|
+
learnings: LearningCandidate[];
|
|
146
|
+
policy?: {
|
|
147
|
+
minConfidence?: number;
|
|
148
|
+
requireVerificationSteps?: boolean;
|
|
149
|
+
maxItems?: number;
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
interface SaveLearningResult {
|
|
153
|
+
saved: Array<{
|
|
154
|
+
id: string;
|
|
155
|
+
kind: MemoryKind;
|
|
156
|
+
title: string;
|
|
157
|
+
deduped: boolean;
|
|
158
|
+
}>;
|
|
159
|
+
rejected: Array<{
|
|
160
|
+
title: string;
|
|
161
|
+
reason: string;
|
|
162
|
+
}>;
|
|
163
|
+
}
|
|
164
|
+
interface MemoryServiceConfig {
|
|
165
|
+
neo4j: {
|
|
166
|
+
uri: string;
|
|
167
|
+
username: string;
|
|
168
|
+
password: string;
|
|
169
|
+
database?: string;
|
|
170
|
+
};
|
|
171
|
+
vectorIndex?: string;
|
|
172
|
+
fulltextIndex?: string;
|
|
173
|
+
halfLifeSeconds?: number;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
declare class MemoryService {
|
|
177
|
+
private client;
|
|
178
|
+
private vectorIndex;
|
|
179
|
+
private fulltextIndex;
|
|
180
|
+
private halfLifeSeconds;
|
|
181
|
+
private cyUpsertMemory;
|
|
182
|
+
private cyUpsertCase;
|
|
183
|
+
private cyRetrieveBundle;
|
|
184
|
+
private cyFeedbackBatch;
|
|
185
|
+
private cyFeedbackCoUsed;
|
|
186
|
+
private cyGetRecallEdges;
|
|
187
|
+
constructor(cfg: MemoryServiceConfig);
|
|
188
|
+
init(): Promise<void>;
|
|
189
|
+
close(): Promise<void>;
|
|
190
|
+
private ensureEnvHash;
|
|
191
|
+
/**
|
|
192
|
+
* Save a distilled memory (semantic/procedural/episodic) with exact dedupe by contentHash.
|
|
193
|
+
* NOTE: This package intentionally does not store "full answers" as semantic/procedural.
|
|
194
|
+
*/
|
|
195
|
+
upsertMemory(l: LearningCandidate & {
|
|
196
|
+
id?: string;
|
|
197
|
+
}): Promise<{
|
|
198
|
+
id: string;
|
|
199
|
+
deduped: boolean;
|
|
200
|
+
}>;
|
|
201
|
+
/**
|
|
202
|
+
* Upsert an episodic Case (case-based reasoning) that links symptoms + env + resolved_by + negative memories.
|
|
203
|
+
*/
|
|
204
|
+
upsertCase(c: CaseRecord): Promise<string>;
|
|
205
|
+
/**
|
|
206
|
+
* Retrieve a ContextBundle with separate Fix and Do-not-do sections, using case-based reasoning.
|
|
207
|
+
* The key idea: match cases by symptoms + env similarity, then pull linked memories.
|
|
208
|
+
*/
|
|
209
|
+
retrieveContextBundle(args: RetrieveContextArgs): Promise<ContextBundle>;
|
|
210
|
+
/**
|
|
211
|
+
* Reinforce/degrade agent->memory association weights using a single batched Cypher query.
|
|
212
|
+
* This supports mid-run retrieval by making feedback cheap and frequent.
|
|
213
|
+
*/
|
|
214
|
+
feedback(fb: MemoryFeedback): Promise<void>;
|
|
215
|
+
/**
|
|
216
|
+
* Save distilled learnings discovered during a task.
|
|
217
|
+
* Enforces quality gates and stores negative memories explicitly.
|
|
218
|
+
* Automatically creates a Case if learnings have triage.symptoms.
|
|
219
|
+
*/
|
|
220
|
+
saveLearnings(req: SaveLearningRequest): Promise<SaveLearningResult>;
|
|
221
|
+
}
|
|
222
|
+
declare function createMemoryService(cfg: MemoryServiceConfig): Promise<MemoryService>;
|
|
223
|
+
|
|
224
|
+
interface Neo4jClientConfig {
|
|
225
|
+
uri: string;
|
|
226
|
+
username: string;
|
|
227
|
+
password: string;
|
|
228
|
+
database?: string;
|
|
229
|
+
}
|
|
230
|
+
declare class Neo4jClient {
|
|
231
|
+
private driver;
|
|
232
|
+
private database?;
|
|
233
|
+
constructor(cfg: Neo4jClientConfig);
|
|
234
|
+
session(mode?: "READ" | "WRITE"): Session;
|
|
235
|
+
close(): Promise<void>;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
declare function ensureSchema(client: Neo4jClient): Promise<void>;
|
|
239
|
+
|
|
240
|
+
declare function sha256Hex(s: string): string;
|
|
241
|
+
declare function canonicaliseForHash(title: string, content: string, tags: string[]): string;
|
|
242
|
+
declare function newId(prefix: string): string;
|
|
243
|
+
declare function normaliseSymptom(s: string): string;
|
|
244
|
+
declare function envHash(env: EnvironmentFingerprint): string;
|
|
245
|
+
|
|
246
|
+
export { type BetaEdge, type CaseRecord, type ContextBundle, type ContextMemoryBase, type ContextMemorySummary, type DistilledInvariant, type EnvironmentFingerprint, type FeedbackMetrics, type LearningCandidate, type MemoryFeedback, type MemoryKind, type MemoryPolarity, type MemoryRecord, MemoryService, type MemoryServiceConfig, Neo4jClient, type Neo4jClientConfig, type RetrieveContextArgs, type SaveLearningRequest, type SaveLearningResult, canonicaliseForHash, createMemoryService, ensureSchema, envHash, newId, normaliseSymptom, sha256Hex };
|
package/dist/index.d.ts
CHANGED
|
@@ -195,6 +195,15 @@ interface SaveLearningResult {
|
|
|
195
195
|
reason: string;
|
|
196
196
|
}>;
|
|
197
197
|
}
|
|
198
|
+
interface AutoRelateConfig {
|
|
199
|
+
enabled?: boolean;
|
|
200
|
+
minSharedTags?: number;
|
|
201
|
+
minWeight?: number;
|
|
202
|
+
maxCandidates?: number;
|
|
203
|
+
sameKind?: boolean;
|
|
204
|
+
samePolarity?: boolean;
|
|
205
|
+
allowedKinds?: MemoryKind[];
|
|
206
|
+
}
|
|
198
207
|
interface MemoryServiceConfig {
|
|
199
208
|
neo4j: {
|
|
200
209
|
uri: string;
|
|
@@ -205,6 +214,7 @@ interface MemoryServiceConfig {
|
|
|
205
214
|
vectorIndex?: string;
|
|
206
215
|
fulltextIndex?: string;
|
|
207
216
|
halfLifeSeconds?: number;
|
|
217
|
+
autoRelate?: AutoRelateConfig;
|
|
208
218
|
onMemoryEvent?: (event: MemoryEvent) => void;
|
|
209
219
|
}
|
|
210
220
|
type MemoryToolName = "store_skill" | "store_pattern" | "store_concept" | "relate_concepts" | "recall_skills" | "recall_concepts" | "recall_patterns";
|
|
@@ -222,6 +232,7 @@ declare class MemoryService {
|
|
|
222
232
|
private fulltextIndex;
|
|
223
233
|
private halfLifeSeconds;
|
|
224
234
|
private onMemoryEvent?;
|
|
235
|
+
private autoRelateConfig;
|
|
225
236
|
private cyUpsertMemory;
|
|
226
237
|
private cyUpsertCase;
|
|
227
238
|
private cyRetrieveBundle;
|
|
@@ -229,6 +240,7 @@ declare class MemoryService {
|
|
|
229
240
|
private cyFeedbackCoUsed;
|
|
230
241
|
private cyListMemories;
|
|
231
242
|
private cyRelateConcepts;
|
|
243
|
+
private cyAutoRelateByTags;
|
|
232
244
|
private cyGetRecallEdges;
|
|
233
245
|
constructor(cfg: MemoryServiceConfig);
|
|
234
246
|
init(): Promise<void>;
|
|
@@ -307,6 +319,7 @@ declare const cypher: {
|
|
|
307
319
|
feedbackCoUsed: string;
|
|
308
320
|
listMemories: string;
|
|
309
321
|
relateConcepts: string;
|
|
322
|
+
autoRelateByTags: string;
|
|
310
323
|
};
|
|
311
324
|
|
|
312
325
|
declare function createMemoryTools(service: MemoryService): MemoryToolSet;
|
|
@@ -317,4 +330,4 @@ declare function newId(prefix: string): string;
|
|
|
317
330
|
declare function normaliseSymptom(s: string): string;
|
|
318
331
|
declare function envHash(env: EnvironmentFingerprint): string;
|
|
319
332
|
|
|
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 };
|
|
333
|
+
export { type AutoRelateConfig, 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 };
|
package/dist/index.js
CHANGED
|
@@ -35,7 +35,8 @@ 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")
|
|
39
40
|
};
|
|
40
41
|
|
|
41
42
|
// src/neo4j/schema.ts
|
|
@@ -95,6 +96,17 @@ function envHash(env) {
|
|
|
95
96
|
function clamp01(x) {
|
|
96
97
|
return Math.max(0, Math.min(1, x));
|
|
97
98
|
}
|
|
99
|
+
var DEFAULT_AUTO_RELATE = {
|
|
100
|
+
enabled: true,
|
|
101
|
+
minSharedTags: 2,
|
|
102
|
+
minWeight: 0.2,
|
|
103
|
+
maxCandidates: 12,
|
|
104
|
+
sameKind: true,
|
|
105
|
+
samePolarity: true,
|
|
106
|
+
allowedKinds: ["semantic", "procedural"]
|
|
107
|
+
};
|
|
108
|
+
var AUTO_RELATE_MIN_SHARED_TAGS = 1;
|
|
109
|
+
var AUTO_RELATE_MIN_MAX_CANDIDATES = 1;
|
|
98
110
|
function toBetaEdge(raw) {
|
|
99
111
|
const aMin = 1e-3;
|
|
100
112
|
const bMin = 1e-3;
|
|
@@ -172,6 +184,7 @@ var MemoryService = class {
|
|
|
172
184
|
fulltextIndex;
|
|
173
185
|
halfLifeSeconds;
|
|
174
186
|
onMemoryEvent;
|
|
187
|
+
autoRelateConfig;
|
|
175
188
|
cyUpsertMemory = cypher.upsertMemory;
|
|
176
189
|
cyUpsertCase = cypher.upsertCase;
|
|
177
190
|
cyRetrieveBundle = cypher.retrieveContextBundle;
|
|
@@ -179,6 +192,7 @@ var MemoryService = class {
|
|
|
179
192
|
cyFeedbackCoUsed = cypher.feedbackCoUsed;
|
|
180
193
|
cyListMemories = cypher.listMemories;
|
|
181
194
|
cyRelateConcepts = cypher.relateConcepts;
|
|
195
|
+
cyAutoRelateByTags = cypher.autoRelateByTags;
|
|
182
196
|
cyGetRecallEdges = `
|
|
183
197
|
UNWIND $ids AS id
|
|
184
198
|
MATCH (m:Memory {id:id})
|
|
@@ -196,6 +210,22 @@ var MemoryService = class {
|
|
|
196
210
|
this.fulltextIndex = cfg.fulltextIndex ?? "memoryText";
|
|
197
211
|
this.halfLifeSeconds = cfg.halfLifeSeconds ?? 30 * 24 * 3600;
|
|
198
212
|
this.onMemoryEvent = cfg.onMemoryEvent;
|
|
213
|
+
const autoRelate = cfg.autoRelate ?? {};
|
|
214
|
+
this.autoRelateConfig = {
|
|
215
|
+
enabled: autoRelate.enabled ?? DEFAULT_AUTO_RELATE.enabled,
|
|
216
|
+
minSharedTags: Math.max(
|
|
217
|
+
AUTO_RELATE_MIN_SHARED_TAGS,
|
|
218
|
+
Math.floor(autoRelate.minSharedTags ?? DEFAULT_AUTO_RELATE.minSharedTags)
|
|
219
|
+
),
|
|
220
|
+
minWeight: clamp01(autoRelate.minWeight ?? DEFAULT_AUTO_RELATE.minWeight),
|
|
221
|
+
maxCandidates: Math.max(
|
|
222
|
+
AUTO_RELATE_MIN_MAX_CANDIDATES,
|
|
223
|
+
Math.floor(autoRelate.maxCandidates ?? DEFAULT_AUTO_RELATE.maxCandidates)
|
|
224
|
+
),
|
|
225
|
+
sameKind: autoRelate.sameKind ?? DEFAULT_AUTO_RELATE.sameKind,
|
|
226
|
+
samePolarity: autoRelate.samePolarity ?? DEFAULT_AUTO_RELATE.samePolarity,
|
|
227
|
+
allowedKinds: autoRelate.allowedKinds ?? [...DEFAULT_AUTO_RELATE.allowedKinds]
|
|
228
|
+
};
|
|
199
229
|
}
|
|
200
230
|
async init() {
|
|
201
231
|
await ensureSchema(this.client);
|
|
@@ -280,6 +310,21 @@ var MemoryService = class {
|
|
|
280
310
|
}
|
|
281
311
|
);
|
|
282
312
|
}
|
|
313
|
+
const autoRelate = this.autoRelateConfig;
|
|
314
|
+
const allowedKinds = autoRelate.allowedKinds ?? [];
|
|
315
|
+
const canAutoRelate = autoRelate.enabled && tags.length >= autoRelate.minSharedTags && (allowedKinds.length === 0 || allowedKinds.includes(l.kind));
|
|
316
|
+
if (canAutoRelate) {
|
|
317
|
+
await write.run(this.cyAutoRelateByTags, {
|
|
318
|
+
id,
|
|
319
|
+
nowIso: (/* @__PURE__ */ new Date()).toISOString(),
|
|
320
|
+
minSharedTags: autoRelate.minSharedTags,
|
|
321
|
+
minWeight: autoRelate.minWeight,
|
|
322
|
+
maxCandidates: autoRelate.maxCandidates,
|
|
323
|
+
sameKind: autoRelate.sameKind,
|
|
324
|
+
samePolarity: autoRelate.samePolarity,
|
|
325
|
+
allowedKinds
|
|
326
|
+
});
|
|
327
|
+
}
|
|
283
328
|
this.emit({ type: "write", action: "upsertMemory", meta: { id } });
|
|
284
329
|
return { id, deduped: false };
|
|
285
330
|
} finally {
|