@knowall-ai/reverie 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/LICENSE +21 -0
- package/README.md +528 -0
- package/build/cypher-guard.js +92 -0
- package/build/embeddings.js +303 -0
- package/build/handlers/index.js +654 -0
- package/build/hygiene.js +43 -0
- package/build/index.js +65 -0
- package/build/neo4j-client.js +182 -0
- package/build/search.js +92 -0
- package/build/server.js +71 -0
- package/build/tools/definitions.js +251 -0
- package/build/tools/guidance-tool.js +284 -0
- package/build/types.js +115 -0
- package/package.json +63 -0
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
import { guidanceTool } from './guidance-tool.js';
|
|
2
|
+
/**
|
|
3
|
+
* MCP Neo4j Agent Memory Tools
|
|
4
|
+
*
|
|
5
|
+
* Tool descriptions are kept simple to avoid breaking prompt templates in 3rd party solutions.
|
|
6
|
+
* Use the get_guidance tool to access detailed information about labels, relationships, and best practices.
|
|
7
|
+
*/
|
|
8
|
+
export const tools = [
|
|
9
|
+
{
|
|
10
|
+
name: 'search_memories',
|
|
11
|
+
description: 'Hybrid keyword + semantic search across the knowledge graph. "Ben Weeks" can also find "Benjamin Weeks" and each result includes _score and _match.',
|
|
12
|
+
inputSchema: {
|
|
13
|
+
type: 'object',
|
|
14
|
+
properties: {
|
|
15
|
+
query: {
|
|
16
|
+
type: 'string',
|
|
17
|
+
description: 'Search text to find in any property. Keyword mode matches any word, while semantic mode can surface close meanings and name variants.',
|
|
18
|
+
},
|
|
19
|
+
label: {
|
|
20
|
+
type: 'string',
|
|
21
|
+
description: 'Filter by memory label',
|
|
22
|
+
},
|
|
23
|
+
depth: {
|
|
24
|
+
type: 'integer',
|
|
25
|
+
minimum: 0,
|
|
26
|
+
maximum: 5,
|
|
27
|
+
description: 'Relationship depth to include, 0 to 5, defaults to 1',
|
|
28
|
+
},
|
|
29
|
+
order_by: {
|
|
30
|
+
type: 'string',
|
|
31
|
+
description: 'Sort order such as created_at DESC, name ASC',
|
|
32
|
+
},
|
|
33
|
+
limit: {
|
|
34
|
+
type: 'integer',
|
|
35
|
+
minimum: 1,
|
|
36
|
+
maximum: 200,
|
|
37
|
+
description: 'Maximum results to return, 1 to 200, defaults to 10',
|
|
38
|
+
},
|
|
39
|
+
since_date: {
|
|
40
|
+
type: 'string',
|
|
41
|
+
description: 'ISO date string to filter memories created after this date (e.g., "2024-01-01" or "2024-01-01T00:00:00Z")',
|
|
42
|
+
},
|
|
43
|
+
search_mode: {
|
|
44
|
+
type: 'string',
|
|
45
|
+
enum: ['hybrid', 'keyword', 'semantic', 'exact'],
|
|
46
|
+
description: 'Search mode: hybrid (default), keyword-only, semantic-only, or exact (case-insensitive equality on name/aliases/email: use before creating a memory).',
|
|
47
|
+
},
|
|
48
|
+
similarity_threshold: {
|
|
49
|
+
type: 'number',
|
|
50
|
+
minimum: 0,
|
|
51
|
+
maximum: 1,
|
|
52
|
+
description: 'Semantic similarity threshold, 0 to 1 inclusive, defaults to 0.4.',
|
|
53
|
+
},
|
|
54
|
+
include_archived: {
|
|
55
|
+
type: 'boolean',
|
|
56
|
+
description: 'Include memories with status "archived" (excluded by default).',
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
required: [],
|
|
60
|
+
additionalProperties: false,
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
name: 'create_memory',
|
|
65
|
+
description: 'Create a new memory in the knowledge graph. Consider that the memory might already exist, so Search → Create → Connect (its important to try and connect memories)',
|
|
66
|
+
inputSchema: {
|
|
67
|
+
type: 'object',
|
|
68
|
+
properties: {
|
|
69
|
+
label: {
|
|
70
|
+
type: 'string',
|
|
71
|
+
description: 'Memory label: a plain identifier, Capitalised singular by convention (Person, Place, Organization, Project, Event, Topic, Object, Animal, Concept, Meeting, Decision…). Use list_memory_labels first for consistency; dream canonicalises lowercase labels.',
|
|
72
|
+
},
|
|
73
|
+
properties: {
|
|
74
|
+
type: 'object',
|
|
75
|
+
description: 'Information to store about this memory (use "name" as primary identifier, e.g. {name: "John Smith", age: 30, occupation: "Engineer"})',
|
|
76
|
+
additionalProperties: true,
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
required: ['label', 'properties'],
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
name: 'create_connection',
|
|
84
|
+
description: 'Create a connection between two memories (its good to have connected memories)',
|
|
85
|
+
inputSchema: {
|
|
86
|
+
type: 'object',
|
|
87
|
+
properties: {
|
|
88
|
+
fromMemoryId: {
|
|
89
|
+
type: 'number',
|
|
90
|
+
description: 'ID of the source memory',
|
|
91
|
+
},
|
|
92
|
+
toMemoryId: {
|
|
93
|
+
type: 'number',
|
|
94
|
+
description: 'ID of the target memory',
|
|
95
|
+
},
|
|
96
|
+
type: {
|
|
97
|
+
type: 'string',
|
|
98
|
+
description: 'Relationship type such as KNOWS, WORKS_ON, LIVES_IN, HAS_SKILL, PARTICIPATES_IN',
|
|
99
|
+
},
|
|
100
|
+
properties: {
|
|
101
|
+
type: 'object',
|
|
102
|
+
description: 'Optional relationship metadata (e.g. {since: "2023-01", role: "Manager", status: "active"})',
|
|
103
|
+
additionalProperties: true,
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
required: ['fromMemoryId', 'toMemoryId', 'type'],
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
name: 'update_memory',
|
|
111
|
+
description: 'Update properties of an existing memory such as adding more detail or make a change when you find out something new',
|
|
112
|
+
inputSchema: {
|
|
113
|
+
type: 'object',
|
|
114
|
+
properties: {
|
|
115
|
+
nodeId: {
|
|
116
|
+
type: 'number',
|
|
117
|
+
description: 'ID of the memory to update',
|
|
118
|
+
},
|
|
119
|
+
properties: {
|
|
120
|
+
type: 'object',
|
|
121
|
+
description: 'Properties to update/add',
|
|
122
|
+
additionalProperties: true,
|
|
123
|
+
},
|
|
124
|
+
},
|
|
125
|
+
required: ['nodeId', 'properties'],
|
|
126
|
+
},
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
name: 'update_connection',
|
|
130
|
+
description: 'Update properties of an existing connection between memories',
|
|
131
|
+
inputSchema: {
|
|
132
|
+
type: 'object',
|
|
133
|
+
properties: {
|
|
134
|
+
fromMemoryId: {
|
|
135
|
+
type: 'number',
|
|
136
|
+
description: 'ID of the source memory',
|
|
137
|
+
},
|
|
138
|
+
toMemoryId: {
|
|
139
|
+
type: 'number',
|
|
140
|
+
description: 'ID of the target memory',
|
|
141
|
+
},
|
|
142
|
+
type: {
|
|
143
|
+
type: 'string',
|
|
144
|
+
description: 'Relationship type to identify which connection to update (e.g. WORKS_AT, KNOWS, MANAGES)',
|
|
145
|
+
},
|
|
146
|
+
properties: {
|
|
147
|
+
type: 'object',
|
|
148
|
+
description: 'Properties to update/add (e.g. {status: "completed", end_date: "2024-01"})',
|
|
149
|
+
additionalProperties: true,
|
|
150
|
+
},
|
|
151
|
+
},
|
|
152
|
+
required: ['fromMemoryId', 'toMemoryId', 'type', 'properties'],
|
|
153
|
+
},
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
name: 'delete_memory',
|
|
157
|
+
description: 'Delete a memory and all its connections (use with caution - this permanently removes the memory and all its connections)',
|
|
158
|
+
inputSchema: {
|
|
159
|
+
type: 'object',
|
|
160
|
+
properties: {
|
|
161
|
+
nodeId: {
|
|
162
|
+
type: 'number',
|
|
163
|
+
description: 'ID of the memory to delete',
|
|
164
|
+
},
|
|
165
|
+
},
|
|
166
|
+
required: ['nodeId'],
|
|
167
|
+
},
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
name: 'delete_connection',
|
|
171
|
+
description: 'Delete a specific connection between two memories (use with caution - this permanently removes the relationship)',
|
|
172
|
+
inputSchema: {
|
|
173
|
+
type: 'object',
|
|
174
|
+
properties: {
|
|
175
|
+
fromMemoryId: {
|
|
176
|
+
type: 'number',
|
|
177
|
+
description: 'ID of the source memory',
|
|
178
|
+
},
|
|
179
|
+
toMemoryId: {
|
|
180
|
+
type: 'number',
|
|
181
|
+
description: 'ID of the target memory',
|
|
182
|
+
},
|
|
183
|
+
type: {
|
|
184
|
+
type: 'string',
|
|
185
|
+
description: 'Exact relationship type to delete (e.g. WORKS_AT, KNOWS, MANAGES)',
|
|
186
|
+
},
|
|
187
|
+
},
|
|
188
|
+
required: ['fromMemoryId', 'toMemoryId', 'type'],
|
|
189
|
+
},
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
name: 'list_memory_labels',
|
|
193
|
+
description: 'List all unique memory labels currently in use with their counts (useful for getting an overview of the knowledge graph)',
|
|
194
|
+
inputSchema: {
|
|
195
|
+
type: 'object',
|
|
196
|
+
properties: {
|
|
197
|
+
include_archived: {
|
|
198
|
+
type: 'boolean',
|
|
199
|
+
description: 'Include labels of archived memories (excluded by default).',
|
|
200
|
+
},
|
|
201
|
+
},
|
|
202
|
+
required: [],
|
|
203
|
+
},
|
|
204
|
+
},
|
|
205
|
+
{
|
|
206
|
+
name: 'query_memories',
|
|
207
|
+
description: 'Run a read-only Cypher query and return up to 200 scrubbed rows.',
|
|
208
|
+
inputSchema: {
|
|
209
|
+
type: 'object',
|
|
210
|
+
properties: {
|
|
211
|
+
cypher: {
|
|
212
|
+
type: 'string',
|
|
213
|
+
description: 'Read-only Cypher to execute.',
|
|
214
|
+
},
|
|
215
|
+
params: {
|
|
216
|
+
type: 'object',
|
|
217
|
+
description: 'Optional parameter map for the Cypher query.',
|
|
218
|
+
additionalProperties: true,
|
|
219
|
+
},
|
|
220
|
+
},
|
|
221
|
+
required: ['cypher'],
|
|
222
|
+
additionalProperties: false,
|
|
223
|
+
},
|
|
224
|
+
},
|
|
225
|
+
{
|
|
226
|
+
name: 'memory_stats',
|
|
227
|
+
description: 'Summarize node, relationship, label, embedding, and orphan counts for the graph.',
|
|
228
|
+
inputSchema: {
|
|
229
|
+
type: 'object',
|
|
230
|
+
properties: {},
|
|
231
|
+
required: [],
|
|
232
|
+
additionalProperties: false,
|
|
233
|
+
},
|
|
234
|
+
},
|
|
235
|
+
{
|
|
236
|
+
name: 'dream',
|
|
237
|
+
description: 'Deterministically relabel, merge duplicates, and refresh embeddings, with an optional dry run report.',
|
|
238
|
+
inputSchema: {
|
|
239
|
+
type: 'object',
|
|
240
|
+
properties: {
|
|
241
|
+
dry_run: {
|
|
242
|
+
type: 'boolean',
|
|
243
|
+
description: 'If true, report planned changes without writing them.',
|
|
244
|
+
},
|
|
245
|
+
},
|
|
246
|
+
required: [],
|
|
247
|
+
additionalProperties: false,
|
|
248
|
+
},
|
|
249
|
+
},
|
|
250
|
+
guidanceTool,
|
|
251
|
+
];
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
export const guidanceTool = {
|
|
2
|
+
name: 'get_guidance',
|
|
3
|
+
description: 'Get help on using the memory tools effectively',
|
|
4
|
+
inputSchema: {
|
|
5
|
+
type: 'object',
|
|
6
|
+
properties: {
|
|
7
|
+
topic: {
|
|
8
|
+
type: 'string',
|
|
9
|
+
description: 'Topic: connections, labels, relationships, best-practices, examples, or leave empty for all',
|
|
10
|
+
},
|
|
11
|
+
},
|
|
12
|
+
required: [],
|
|
13
|
+
},
|
|
14
|
+
};
|
|
15
|
+
export function getGuidanceContent(topic) {
|
|
16
|
+
const sections = {
|
|
17
|
+
connections: `## The Power of Connections
|
|
18
|
+
|
|
19
|
+
**Why Connections Matter**
|
|
20
|
+
- Isolated memories = limited utility
|
|
21
|
+
- Connected memories = exponential value
|
|
22
|
+
- Each connection adds context and meaning
|
|
23
|
+
- Enables discovery of hidden patterns
|
|
24
|
+
|
|
25
|
+
**Connection Strategy**
|
|
26
|
+
1. Create the memory first
|
|
27
|
+
2. Immediately ask: "What does this relate to?"
|
|
28
|
+
3. Search for related memories
|
|
29
|
+
4. Create meaningful connections
|
|
30
|
+
5. Add properties to connections for context
|
|
31
|
+
|
|
32
|
+
**Example Flow**
|
|
33
|
+
1. User: "John works at Google as a software engineer"
|
|
34
|
+
2. **Search for John first** - he might already exist!
|
|
35
|
+
3. If not found, create memory for John (person)
|
|
36
|
+
4. **Search for Google** - it probably already exists
|
|
37
|
+
5. If not found, create Google (organization)
|
|
38
|
+
6. Connect: John -[WORKS_AT]-> Google
|
|
39
|
+
7. Add properties: role="Software Engineer", since="2020"
|
|
40
|
+
|
|
41
|
+
**Connection Patterns**
|
|
42
|
+
- Person -> Organization (WORKS_AT, FOUNDED, OWNS)
|
|
43
|
+
- Person -> Person (KNOWS, MANAGES, MARRIED_TO)
|
|
44
|
+
- Person -> Skill (HAS_SKILL)
|
|
45
|
+
- Person -> Place (LIVES_IN, FROM)
|
|
46
|
+
- Project -> Person (LED_BY, INVOLVES)
|
|
47
|
+
- Event -> Person (ATTENDED_BY, ORGANIZED_BY)`,
|
|
48
|
+
labels: `## Common Memory Labels
|
|
49
|
+
|
|
50
|
+
Use these labels when creating memories (Capitalised singular is canonical; lowercase is accepted and canonicalised by dream):
|
|
51
|
+
|
|
52
|
+
**People & Living Things**
|
|
53
|
+
- person: Individual people
|
|
54
|
+
- animal: Pets, wildlife
|
|
55
|
+
- plant: Trees, flowers, crops
|
|
56
|
+
|
|
57
|
+
**Places & Locations**
|
|
58
|
+
- place: General locations
|
|
59
|
+
- organization: Companies, institutions
|
|
60
|
+
- vehicle: Cars, planes, boats
|
|
61
|
+
|
|
62
|
+
**Concepts & Activities**
|
|
63
|
+
- project: Work or personal projects
|
|
64
|
+
- event: Meetings, conferences, celebrations
|
|
65
|
+
- topic: Subjects of interest
|
|
66
|
+
- activity: Sports, hobbies
|
|
67
|
+
- skill: Abilities and expertise
|
|
68
|
+
|
|
69
|
+
**Objects & Media**
|
|
70
|
+
- object: Physical items
|
|
71
|
+
- food: Meals, ingredients
|
|
72
|
+
- media: Books, movies, music
|
|
73
|
+
- document: Files, reports
|
|
74
|
+
- tool: Software, hardware
|
|
75
|
+
|
|
76
|
+
**Abstract Concepts**
|
|
77
|
+
- idea: Thoughts, concepts
|
|
78
|
+
- goal: Objectives, targets
|
|
79
|
+
- task: To-dos, assignments
|
|
80
|
+
- habit: Routines, behaviors
|
|
81
|
+
- health: Medical, wellness`,
|
|
82
|
+
relationships: `## Common Relationship Types
|
|
83
|
+
|
|
84
|
+
Use UPPERCASE for relationship types:
|
|
85
|
+
|
|
86
|
+
**Personal Relationships**
|
|
87
|
+
- KNOWS: General acquaintance
|
|
88
|
+
- FRIENDS_WITH: Close friendship
|
|
89
|
+
- MARRIED_TO: Spouse relationship
|
|
90
|
+
- FAMILY_OF: Family connection
|
|
91
|
+
|
|
92
|
+
**Professional Relationships**
|
|
93
|
+
- WORKS_AT: Employment
|
|
94
|
+
- WORKS_ON: Project involvement
|
|
95
|
+
- COLLABORATES_WITH: Working together
|
|
96
|
+
- MANAGES: Leadership role
|
|
97
|
+
- REPORTS_TO: Reporting structure
|
|
98
|
+
- OWNS: Ownership
|
|
99
|
+
|
|
100
|
+
**Location Relationships**
|
|
101
|
+
- LIVES_IN: Residence
|
|
102
|
+
- LOCATED_IN: Physical location
|
|
103
|
+
- VISITED: Past visits
|
|
104
|
+
- FROM: Origin
|
|
105
|
+
|
|
106
|
+
**Skill & Knowledge**
|
|
107
|
+
- HAS_SKILL: Possesses ability
|
|
108
|
+
- TEACHES: Instructing others
|
|
109
|
+
- LEARNS_FROM: Student relationship
|
|
110
|
+
|
|
111
|
+
**Project & Creation**
|
|
112
|
+
- LEADS: Project leadership
|
|
113
|
+
- PARTICIPATES_IN: Involvement
|
|
114
|
+
- CREATED: Authorship
|
|
115
|
+
- USES: Utilization`,
|
|
116
|
+
'best-practices': `## Best Practices
|
|
117
|
+
|
|
118
|
+
**CRITICAL: Always Create Connections**
|
|
119
|
+
- Isolated memories have limited value - ALWAYS look for connections
|
|
120
|
+
- When storing new information, immediately think: "What does this relate to?"
|
|
121
|
+
- A memory without connections is like a book with no catalog entry
|
|
122
|
+
- The graph becomes exponentially more useful with each connection
|
|
123
|
+
|
|
124
|
+
**Creating Memories - ALWAYS SEARCH FIRST**
|
|
125
|
+
- **CRITICAL: Always search before creating** - memories often already exist
|
|
126
|
+
- Search by name first: search_memories({query: "John Smith"})
|
|
127
|
+
- If multiple matches found, **ASK THE USER TO CONFIRM** which one they mean
|
|
128
|
+
- Show distinguishing details: "I found 3 people named John: John Smith (Engineer at Google), John Smith (Doctor), John Smith (Teacher). Which one did you mean?"
|
|
129
|
+
- If unsure about a match, describe it and ask: "I found John Smith who works at TechCorp. Is this the same person?"
|
|
130
|
+
- Only create new memory after confirming it's not a duplicate
|
|
131
|
+
- Labels are plain identifiers; Capitalised singular is canonical (Person, Organization, Concept)
|
|
132
|
+
- Include a 'name' property for easy identification
|
|
133
|
+
- 'created_at' is automatic if not provided
|
|
134
|
+
- IMMEDIATELY after creating, connect it to related memories
|
|
135
|
+
|
|
136
|
+
**What belongs in the graph**
|
|
137
|
+
- The graph is a map of entities and how they relate: people, organisations, projects, products, places, animals, concepts. It is not a notebook
|
|
138
|
+
- A property is a durable attribute of an entity: email, role, phone, website, birthday, a preference you'd still want next year
|
|
139
|
+
- Episodes (a call, an email thread, a day's events) mostly belong in your notes or diary. Store in the graph only what they taught you about an entity or a relationship, kept short
|
|
140
|
+
- Prefer a relationship over a property when the fact is about two entities: Ben -[USES {note: "camera on, wants fixes verified"}]-> Teams calls, rather than a paragraph on Ben
|
|
141
|
+
- Make an episode its own memory (e.g. meeting, decision) only when the episode itself is something you'll need to find again
|
|
142
|
+
- A node with dozens of properties is a smell: update_memory returns a _hint past the limit, and dream lists such nodes under "bloated". When you sleep, fold each fact-like key into a short attribute, a relationship, or your notes, then remove it
|
|
143
|
+
|
|
144
|
+
**Building Connections**
|
|
145
|
+
- After creating any memory, ask: "What existing memories relate to this?"
|
|
146
|
+
- Create connections for: people→organizations, people→projects, people→skills
|
|
147
|
+
- Add relationship properties for rich context (since, role, status)
|
|
148
|
+
- One memory can have many connections - don't limit yourself
|
|
149
|
+
|
|
150
|
+
**Searching**
|
|
151
|
+
- Empty query string returns all memories
|
|
152
|
+
- Use search_mode: "hybrid" (the default) to return keyword matches first, then semantic matches above the threshold
|
|
153
|
+
- Use search_mode: "keyword" to match any word of the query as a substring of any searchable content property (not timestamps, status or embedding fields), or search_mode: "semantic" for meaning-first recall
|
|
154
|
+
- Use search_mode: "exact" before creating: it matches only a memory whose name, alias or email equals the query, case-insensitively
|
|
155
|
+
- Archived memories are excluded unless include_archived: true
|
|
156
|
+
- Lower similarity_threshold (for example 0.35) to widen semantic matches, or raise it (for example 0.7) to be stricter
|
|
157
|
+
- Results include _score and _match so you can explain why something was returned
|
|
158
|
+
- Use label parameter to filter by type
|
|
159
|
+
- Increase depth to include more relationships (depth=2 or 3 for rich context)
|
|
160
|
+
- Default limit is 10, max is 200
|
|
161
|
+
|
|
162
|
+
**Managing Relationships**
|
|
163
|
+
- Use UPPERCASE for relationship types
|
|
164
|
+
- Add properties to relationships for context (e.g., since, role)
|
|
165
|
+
- Search for memory IDs before creating connections
|
|
166
|
+
- Use list_memory_labels to see existing labels
|
|
167
|
+
|
|
168
|
+
**Data Organization**
|
|
169
|
+
- Keep labels simple and consistent
|
|
170
|
+
- Reuse existing labels when possible
|
|
171
|
+
- Add descriptive properties to memories
|
|
172
|
+
- Use relationships instead of complex properties
|
|
173
|
+
- Think in graphs: nodes (memories) + edges (relationships) = knowledge`,
|
|
174
|
+
examples: `## Examples
|
|
175
|
+
|
|
176
|
+
**Creating a Person (WITH SEARCH FIRST)**
|
|
177
|
+
\`\`\`
|
|
178
|
+
// ALWAYS search first to avoid duplicates
|
|
179
|
+
const searchResult = search_memories({ query: "Alice Johnson" })
|
|
180
|
+
|
|
181
|
+
if (searchResult.length === 0) {
|
|
182
|
+
// Only create if not found
|
|
183
|
+
create_memory({
|
|
184
|
+
label: "person",
|
|
185
|
+
properties: {
|
|
186
|
+
name: "Alice Johnson",
|
|
187
|
+
occupation: "Software Engineer",
|
|
188
|
+
company: "Tech Corp"
|
|
189
|
+
}
|
|
190
|
+
})
|
|
191
|
+
} else {
|
|
192
|
+
// Use existing memory ID: searchResult[0].memory._id
|
|
193
|
+
}
|
|
194
|
+
\`\`\`
|
|
195
|
+
|
|
196
|
+
**Creating a Relationship**
|
|
197
|
+
\`\`\`
|
|
198
|
+
// First, find the IDs
|
|
199
|
+
search_memories({ query: "Alice Johnson" })
|
|
200
|
+
search_memories({ query: "Tech Corp" })
|
|
201
|
+
|
|
202
|
+
// Then connect them
|
|
203
|
+
create_connection({
|
|
204
|
+
fromMemoryId: 123,
|
|
205
|
+
toMemoryId: 456,
|
|
206
|
+
type: "WORKS_AT",
|
|
207
|
+
properties: {
|
|
208
|
+
role: "Senior Engineer",
|
|
209
|
+
since: "2020-01-15"
|
|
210
|
+
}
|
|
211
|
+
})
|
|
212
|
+
\`\`\`
|
|
213
|
+
|
|
214
|
+
**Handling Ambiguous Matches**
|
|
215
|
+
\`\`\`
|
|
216
|
+
// User says: "Add that Sarah works at Microsoft"
|
|
217
|
+
const searchResult = search_memories({ query: "Sarah" })
|
|
218
|
+
|
|
219
|
+
// Found multiple matches - ASK USER TO CONFIRM
|
|
220
|
+
if (searchResult.length > 1) {
|
|
221
|
+
// Show options to user:
|
|
222
|
+
// "I found 3 people named Sarah:
|
|
223
|
+
// 1. Sarah Johnson (Marketing Manager at TechCorp)
|
|
224
|
+
// 2. Sarah Chen (Data Scientist)
|
|
225
|
+
// 3. Sarah Williams (Designer at StartupXYZ)
|
|
226
|
+
// Which Sarah works at Microsoft?"
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// If only one match but uncertain
|
|
230
|
+
if (searchResult.length === 1) {
|
|
231
|
+
// Confirm with user:
|
|
232
|
+
// "I found Sarah Johnson who is a Marketing Manager at TechCorp.
|
|
233
|
+
// Is this the Sarah who now works at Microsoft?"
|
|
234
|
+
}
|
|
235
|
+
\`\`\`
|
|
236
|
+
|
|
237
|
+
**Searching with Depth**
|
|
238
|
+
\`\`\`
|
|
239
|
+
// Find all people and their connections
|
|
240
|
+
search_memories({
|
|
241
|
+
query: "",
|
|
242
|
+
label: "person",
|
|
243
|
+
depth: 2,
|
|
244
|
+
limit: 50
|
|
245
|
+
})
|
|
246
|
+
\`\`\`
|
|
247
|
+
|
|
248
|
+
**Updating a Memory**
|
|
249
|
+
\`\`\`
|
|
250
|
+
update_memory({
|
|
251
|
+
nodeId: 123,
|
|
252
|
+
properties: {
|
|
253
|
+
occupation: "Lead Engineer",
|
|
254
|
+
skills: ["Python", "Neo4j", "MCP"]
|
|
255
|
+
}
|
|
256
|
+
})
|
|
257
|
+
\`\`\``,
|
|
258
|
+
};
|
|
259
|
+
if (!topic) {
|
|
260
|
+
// Return all sections
|
|
261
|
+
return `# Neo4j Agent Memory Guidance
|
|
262
|
+
|
|
263
|
+
${sections.connections}
|
|
264
|
+
|
|
265
|
+
${sections.labels}
|
|
266
|
+
|
|
267
|
+
${sections.relationships}
|
|
268
|
+
|
|
269
|
+
${sections['best-practices']}
|
|
270
|
+
|
|
271
|
+
${sections.examples}`;
|
|
272
|
+
}
|
|
273
|
+
const content = sections[topic];
|
|
274
|
+
if (content) {
|
|
275
|
+
return content;
|
|
276
|
+
}
|
|
277
|
+
return `Unknown topic: '${topic}'. Available topics: connections, labels, relationships, best-practices, examples, or leave empty for all guidance.`;
|
|
278
|
+
}
|
|
279
|
+
export function isGetGuidanceArgs(args) {
|
|
280
|
+
if (typeof args !== 'object' || args === null)
|
|
281
|
+
return true; // Allow empty args
|
|
282
|
+
const obj = args;
|
|
283
|
+
return obj.topic === undefined || typeof obj.topic === 'string';
|
|
284
|
+
}
|
package/build/types.js
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/** Labels and relationship types are interpolated into Cypher, so they must be plain identifiers. */
|
|
2
|
+
export const IDENTIFIER_RE = /^[A-Za-z_][A-Za-z0-9_]{0,63}$/;
|
|
3
|
+
export function isIdentifier(value) {
|
|
4
|
+
return typeof value === 'string' && IDENTIFIER_RE.test(value);
|
|
5
|
+
}
|
|
6
|
+
/** Backtick-quote a validated identifier for use in Cypher. Throws on anything not validated. */
|
|
7
|
+
export function cypherIdentifier(value) {
|
|
8
|
+
if (!isIdentifier(value)) {
|
|
9
|
+
throw new Error(`Invalid Cypher identifier: ${JSON.stringify(value)}`);
|
|
10
|
+
}
|
|
11
|
+
return '`' + value + '`';
|
|
12
|
+
}
|
|
13
|
+
export const SEARCH_MAX_LIMIT = 200;
|
|
14
|
+
export const SEARCH_MAX_DEPTH = 5;
|
|
15
|
+
function isPlainObject(value) {
|
|
16
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
17
|
+
}
|
|
18
|
+
function hasOnlyKeys(value, allowed) {
|
|
19
|
+
return Object.keys(value).every((key) => allowed.includes(key));
|
|
20
|
+
}
|
|
21
|
+
function isIntegerInRange(value, min, max) {
|
|
22
|
+
return typeof value === 'number' && Number.isInteger(value) && value >= min && value <= max;
|
|
23
|
+
}
|
|
24
|
+
const isNodeId = (value) => isIntegerInRange(value, 0, Number.MAX_SAFE_INTEGER);
|
|
25
|
+
export function isCreateMemoryArgs(args) {
|
|
26
|
+
return isPlainObject(args) &&
|
|
27
|
+
hasOnlyKeys(args, ['label', 'properties']) &&
|
|
28
|
+
isIdentifier(args.label) &&
|
|
29
|
+
isPlainObject(args.properties);
|
|
30
|
+
}
|
|
31
|
+
const SEARCH_KEYS = ['query', 'label', 'depth', 'order_by', 'limit', 'since_date', 'search_mode', 'similarity_threshold', 'include_archived'];
|
|
32
|
+
export function isSearchMemoriesArgs(args) {
|
|
33
|
+
if (!isPlainObject(args) || !hasOnlyKeys(args, SEARCH_KEYS))
|
|
34
|
+
return false;
|
|
35
|
+
const searchArgs = args;
|
|
36
|
+
if (searchArgs.query !== undefined && typeof searchArgs.query !== 'string')
|
|
37
|
+
return false;
|
|
38
|
+
if (searchArgs.label !== undefined && typeof searchArgs.label !== 'string')
|
|
39
|
+
return false;
|
|
40
|
+
if (searchArgs.depth !== undefined && !isIntegerInRange(searchArgs.depth, 0, SEARCH_MAX_DEPTH))
|
|
41
|
+
return false;
|
|
42
|
+
if (searchArgs.order_by !== undefined && typeof searchArgs.order_by !== 'string')
|
|
43
|
+
return false;
|
|
44
|
+
if (searchArgs.limit !== undefined && !isIntegerInRange(searchArgs.limit, 1, SEARCH_MAX_LIMIT))
|
|
45
|
+
return false;
|
|
46
|
+
if (searchArgs.since_date !== undefined && typeof searchArgs.since_date !== 'string')
|
|
47
|
+
return false;
|
|
48
|
+
if (searchArgs.search_mode !== undefined && !['hybrid', 'keyword', 'semantic', 'exact'].includes(searchArgs.search_mode))
|
|
49
|
+
return false;
|
|
50
|
+
if (searchArgs.include_archived !== undefined && typeof searchArgs.include_archived !== 'boolean')
|
|
51
|
+
return false;
|
|
52
|
+
if (searchArgs.similarity_threshold !== undefined) {
|
|
53
|
+
const threshold = searchArgs.similarity_threshold;
|
|
54
|
+
if (typeof threshold !== 'number' || !Number.isFinite(threshold) || threshold < 0 || threshold > 1)
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
return true;
|
|
58
|
+
}
|
|
59
|
+
export function isCreateConnectionArgs(args) {
|
|
60
|
+
return isPlainObject(args) &&
|
|
61
|
+
hasOnlyKeys(args, ['fromMemoryId', 'toMemoryId', 'type', 'properties']) &&
|
|
62
|
+
isNodeId(args.fromMemoryId) &&
|
|
63
|
+
isNodeId(args.toMemoryId) &&
|
|
64
|
+
isIdentifier(args.type) &&
|
|
65
|
+
(args.properties === undefined || isPlainObject(args.properties));
|
|
66
|
+
}
|
|
67
|
+
export function isUpdateMemoryArgs(args) {
|
|
68
|
+
return isPlainObject(args) &&
|
|
69
|
+
hasOnlyKeys(args, ['nodeId', 'properties']) &&
|
|
70
|
+
isNodeId(args.nodeId) &&
|
|
71
|
+
isPlainObject(args.properties);
|
|
72
|
+
}
|
|
73
|
+
export function isUpdateConnectionArgs(args) {
|
|
74
|
+
return isPlainObject(args) &&
|
|
75
|
+
hasOnlyKeys(args, ['fromMemoryId', 'toMemoryId', 'type', 'properties']) &&
|
|
76
|
+
isNodeId(args.fromMemoryId) &&
|
|
77
|
+
isNodeId(args.toMemoryId) &&
|
|
78
|
+
isIdentifier(args.type) &&
|
|
79
|
+
isPlainObject(args.properties);
|
|
80
|
+
}
|
|
81
|
+
export function isDeleteMemoryArgs(args) {
|
|
82
|
+
return isPlainObject(args) && hasOnlyKeys(args, ['nodeId']) && isNodeId(args.nodeId);
|
|
83
|
+
}
|
|
84
|
+
export function isDeleteConnectionArgs(args) {
|
|
85
|
+
return isPlainObject(args) &&
|
|
86
|
+
hasOnlyKeys(args, ['fromMemoryId', 'toMemoryId', 'type']) &&
|
|
87
|
+
isNodeId(args.fromMemoryId) &&
|
|
88
|
+
isNodeId(args.toMemoryId) &&
|
|
89
|
+
isIdentifier(args.type);
|
|
90
|
+
}
|
|
91
|
+
export function isListMemoryLabelsArgs(args) {
|
|
92
|
+
return isPlainObject(args) && hasOnlyKeys(args, ['include_archived']) &&
|
|
93
|
+
(args.include_archived === undefined || typeof args.include_archived === 'boolean');
|
|
94
|
+
}
|
|
95
|
+
export function isQueryMemoriesArgs(args) {
|
|
96
|
+
if (!isPlainObject(args) || !hasOnlyKeys(args, ['cypher', 'params']))
|
|
97
|
+
return false;
|
|
98
|
+
const queryArgs = args;
|
|
99
|
+
if (typeof queryArgs.cypher !== 'string' || queryArgs.cypher.trim() === '')
|
|
100
|
+
return false;
|
|
101
|
+
if (queryArgs.params !== undefined && !isPlainObject(queryArgs.params))
|
|
102
|
+
return false;
|
|
103
|
+
return true;
|
|
104
|
+
}
|
|
105
|
+
export function isMemoryStatsArgs(args) {
|
|
106
|
+
return isPlainObject(args) && hasOnlyKeys(args, []);
|
|
107
|
+
}
|
|
108
|
+
export function isDreamArgs(args) {
|
|
109
|
+
if (!isPlainObject(args) || !hasOnlyKeys(args, ['dry_run']))
|
|
110
|
+
return false;
|
|
111
|
+
const dreamArgs = args;
|
|
112
|
+
if (dreamArgs.dry_run !== undefined && typeof dreamArgs.dry_run !== 'boolean')
|
|
113
|
+
return false;
|
|
114
|
+
return true;
|
|
115
|
+
}
|