@stackflo-labs/n8n-nodes-retainr 0.3.1 → 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 CHANGED
@@ -8,11 +8,11 @@ Give your n8n AI agents **long-term memory** that persists across workflow runs.
8
8
 
9
9
  | Operation | Description |
10
10
  |-----------|-------------|
11
- | **Store** | Save a memory with scope, tags, metadata, TTL, and deduplication |
11
+ | **Store** | Save a memory with namespace, tags, metadata, TTL, and deduplication |
12
12
  | **Search** | Semantic similarity search across stored memories |
13
13
  | **Get Context** | Retrieve pre-formatted memory context ready for LLM system prompts |
14
- | **List** | Browse memories with filters (scope, user, session, tags) |
15
- | **Delete** | Remove memories by filter criteria |
14
+ | **List** | Browse memories with filters (namespace, tags) |
15
+ | **Delete** | Remove memories by namespace |
16
16
  | **Get Workspace Info** | View plan usage, API keys, and workspace details |
17
17
 
18
18
  ## Installation
@@ -20,13 +20,23 @@ Give your n8n AI agents **long-term memory** that persists across workflow runs.
20
20
  ### n8n Cloud & Desktop
21
21
 
22
22
  1. Go to **Settings > Community Nodes**
23
- 2. Search for `@stackflo-labs/n8n-nodes-retainr`
23
+ 2. Click **Install**
24
+ 3. Enter the exact package name: `@stackflo-labs/n8n-nodes-retainr`
25
+ 4. Click **Install** and restart if prompted
26
+
27
+ ### Self-hosted n8n (UI)
28
+
29
+ 1. Go to **Settings > Community Nodes > Install**
30
+ 2. Enter `@stackflo-labs/n8n-nodes-retainr`
24
31
  3. Click **Install**
25
32
 
26
- ### Self-hosted n8n
33
+ Requires `N8N_COMMUNITY_PACKAGES_ENABLED=true` in your environment (set by default in Docker).
34
+
35
+ ### Self-hosted n8n (manual / Docker volume)
27
36
 
28
37
  ```bash
29
- cd ~/.n8n
38
+ # If you mount a custom node directory or manage packages directly:
39
+ cd /home/node/.n8n
30
40
  npm install @stackflo-labs/n8n-nodes-retainr
31
41
  ```
32
42
 
@@ -36,9 +46,18 @@ Then restart n8n.
36
46
 
37
47
  1. Sign up at [retainr.dev](https://retainr.dev) and get your API key
38
48
  2. In n8n, go to **Credentials > New > Retainr API**
39
- 3. Paste your API key (`rec_live_...`)
49
+ 3. Paste your API key (`rec_live_...`) — each key is scoped to one workspace
40
50
  4. The default Base URL (`https://api.retainr.dev`) works out of the box
41
51
 
52
+ ## How it works
53
+
54
+ **Workspace → Namespace** hierarchy:
55
+
56
+ - **Workspace**: Your organization, determined by the API key
57
+ - **Namespace**: Groups memories by customer, tenant, or any identifier (e.g., `customer:alice`, `project:onboarding`)
58
+
59
+ Every memory operation accepts a **Namespace** field. Use it to keep each customer's memories separate within your workspace.
60
+
42
61
  ## Usage Examples
43
62
 
44
63
  ### Store a memory after a conversation
@@ -46,69 +65,27 @@ Then restart n8n.
46
65
  1. Add the **Retainr** node after your AI Agent
47
66
  2. Set **Resource** to `Memory`, **Operation** to `Store`
48
67
  3. Map the conversation summary to the **Content** field
49
- 4. Set **Scope** to `User` and provide the **User ID**
68
+ 4. Set **Namespace** to the customer identifier (e.g., `customer:alice`)
50
69
 
51
70
  ### Inject memory context into an LLM prompt
52
71
 
53
72
  1. Add a **Retainr** node before your AI Agent
54
73
  2. Set **Operation** to `Get Context`
55
74
  3. Use the incoming message as the **Query**
56
- 4. Pass the `context` output into your LLM's system prompt
75
+ 4. Set the same **Namespace** to retrieve that customer's memories
76
+ 5. Pass the `context` output into your LLM's system prompt
57
77
 
58
78
  ### Deduplicate similar memories
59
79
 
60
80
  Use the **Dedup Threshold** field (e.g., `0.95`) when storing. If a sufficiently similar memory already exists, it will be updated instead of duplicated.
61
81
 
62
- ## Memory Scopes
63
-
64
- | Scope | Use case |
65
- |-------|----------|
66
- | `session` | Single workflow run — requires `session_id` |
67
- | `user` | Persists across runs for one user — requires `user_id` |
68
- | `agent` | Shared across users for one agent — requires `agent_id` |
69
- | `global` | Shared across the entire workspace |
70
-
71
82
  ## AI Agent Tool
72
83
 
73
84
  This node has `usableAsTool` enabled — you can use it directly as a tool inside n8n's **AI Agent** node, letting the agent decide when to store or recall memories autonomously.
74
85
 
75
- ## Demo Video
76
-
77
- A recorded demo covering all n8n Creator Portal requirements is at:
78
-
79
- ```
80
- products/retainr/web/public/demo/n8n-demo.mp4
81
- ```
82
-
83
- Full disk path (Windows): `D:\dev\datadir\stackflo\products\retainr\web\public\demo\n8n-demo.mp4`
84
-
85
- **To re-record** (e.g. after a node update):
86
-
87
- ```bash
88
- # 1. Start n8n with the node pre-installed
89
- cd packages/n8n-node
90
- docker compose -f e2e/docker-compose.yml up --build -d
91
-
92
- # 2. Record — no API keys needed, the script handles everything
93
- cd ../../products/retainr/web
94
- N8N_BASE_URL=http://127.0.0.1:5678 \
95
- node scripts/record-n8n-demo.mjs
96
- # → public/demo/n8n-demo-raw.webm
97
-
98
- # 3. Post-process to MP4
99
- bash ../../packages/n8n-node/demo/post-process.sh public/demo/n8n-demo-raw.webm
100
- # → public/demo/n8n-demo.mp4
101
-
102
- # 4. Tear down
103
- cd ../../packages/n8n-node
104
- docker compose -f e2e/docker-compose.yml down
105
- ```
106
-
107
- See `demo/README.md` for full details.
108
-
109
86
  ## API Documentation
110
87
 
111
- Full API reference: [retainr.dev/docs/api](https://retainr.dev/docs/api)
88
+ Full API reference: [retainr.dev/docs](https://retainr.dev/docs)
112
89
 
113
90
  ## License
114
91
 
@@ -136,95 +136,13 @@ class Retainr {
136
136
  },
137
137
  },
138
138
  },
139
- {
140
- displayName: 'Scope',
141
- name: 'scope',
142
- type: 'options',
143
- required: true,
144
- default: 'user',
145
- description: 'Memory visibility scope.',
146
- options: [
147
- {
148
- name: 'Session',
149
- value: 'session',
150
- description: 'Scoped to a single workflow run.',
151
- },
152
- {
153
- name: 'User',
154
- value: 'user',
155
- description: 'Persists across runs for one user.',
156
- },
157
- {
158
- name: 'Agent',
159
- value: 'agent',
160
- description: 'Shared across users for one agent.',
161
- },
162
- {
163
- name: 'Global',
164
- value: 'global',
165
- description: 'Shared across the entire workspace.',
166
- },
167
- ],
168
- displayOptions: {
169
- show: {
170
- resource: ['memory'],
171
- operation: ['store'],
172
- },
173
- },
174
- },
175
- {
176
- displayName: 'Session ID',
177
- name: 'sessionId',
178
- type: 'string',
179
- default: '',
180
- required: true,
181
- description: 'Unique identifier for the session.',
182
- displayOptions: {
183
- show: {
184
- resource: ['memory'],
185
- operation: ['store'],
186
- scope: ['session'],
187
- },
188
- },
189
- },
190
- {
191
- displayName: 'User ID',
192
- name: 'userId',
193
- type: 'string',
194
- default: '',
195
- required: true,
196
- description: 'Unique identifier for the user.',
197
- displayOptions: {
198
- show: {
199
- resource: ['memory'],
200
- operation: ['store'],
201
- scope: ['user'],
202
- },
203
- },
204
- },
205
- {
206
- displayName: 'Agent ID',
207
- name: 'agentId',
208
- type: 'string',
209
- default: '',
210
- required: true,
211
- description: 'Unique identifier for the agent.',
212
- displayOptions: {
213
- show: {
214
- resource: ['memory'],
215
- operation: ['store'],
216
- scope: ['agent'],
217
- },
218
- },
219
- },
220
139
  {
221
140
  displayName: 'Namespace',
222
141
  name: 'namespace',
223
142
  type: 'string',
224
- required: false,
225
143
  default: '',
226
- placeholder: 'sarah-chen-001',
227
- description: 'Customer or tenant identifier within this workspace. Workspace is set by the API key; namespace groups memories per customer.',
144
+ placeholder: 'customer:alice',
145
+ description: 'Group memories by any string. Leave empty for global scope.',
228
146
  displayOptions: {
229
147
  show: {
230
148
  resource: ['memory'],
@@ -309,8 +227,8 @@ class Retainr {
309
227
  type: 'string',
310
228
  required: false,
311
229
  default: '',
312
- placeholder: 'sarah-chen-001',
313
- description: 'Filter by customer/tenant namespace within this workspace.',
230
+ placeholder: 'customer:alice',
231
+ description: 'Group memories by any string. Leave empty for global scope.',
314
232
  displayOptions: {
315
233
  show: {
316
234
  resource: ['memory'],
@@ -331,13 +249,6 @@ class Retainr {
331
249
  },
332
250
  },
333
251
  options: [
334
- {
335
- displayName: 'Agent ID',
336
- name: 'agentId',
337
- type: 'string',
338
- default: '',
339
- description: 'Filter by agent ID.',
340
- },
341
252
  {
342
253
  displayName: 'Limit',
343
254
  name: 'limit',
@@ -348,26 +259,6 @@ class Retainr {
348
259
  default: 50,
349
260
  description: 'Max number of results to return.',
350
261
  },
351
- {
352
- displayName: 'Scope',
353
- name: 'scope',
354
- type: 'options',
355
- options: [
356
- { name: 'Session', value: 'session' },
357
- { name: 'User', value: 'user' },
358
- { name: 'Agent', value: 'agent' },
359
- { name: 'Global', value: 'global' },
360
- ],
361
- default: 'user',
362
- description: 'Filter by scope.',
363
- },
364
- {
365
- displayName: 'Session ID',
366
- name: 'sessionId',
367
- type: 'string',
368
- default: '',
369
- description: 'Filter by session ID.',
370
- },
371
262
  {
372
263
  displayName: 'Tags',
373
264
  name: 'tags',
@@ -386,13 +277,6 @@ class Retainr {
386
277
  default: 0.5,
387
278
  description: 'Minimum similarity threshold (0-1).',
388
279
  },
389
- {
390
- displayName: 'User ID',
391
- name: 'userId',
392
- type: 'string',
393
- default: '',
394
- description: 'Filter by user ID.',
395
- },
396
280
  ],
397
281
  },
398
282
  // ==================================================================
@@ -452,8 +336,8 @@ class Retainr {
452
336
  type: 'string',
453
337
  required: false,
454
338
  default: '',
455
- placeholder: 'sarah-chen-001',
456
- description: 'Filter by customer/tenant namespace within this workspace.',
339
+ placeholder: 'customer:alice',
340
+ description: 'Group memories by any string. Leave empty for global scope.',
457
341
  displayOptions: {
458
342
  show: {
459
343
  resource: ['memory'],
@@ -474,13 +358,6 @@ class Retainr {
474
358
  },
475
359
  },
476
360
  options: [
477
- {
478
- displayName: 'Agent ID',
479
- name: 'agentId',
480
- type: 'string',
481
- default: '',
482
- description: 'Filter by agent ID.',
483
- },
484
361
  {
485
362
  displayName: 'Max Memories',
486
363
  name: 'maxMemories',
@@ -491,26 +368,6 @@ class Retainr {
491
368
  default: 5,
492
369
  description: 'Maximum number of memories to include in the context (API max 20).',
493
370
  },
494
- {
495
- displayName: 'Scope',
496
- name: 'scope',
497
- type: 'options',
498
- options: [
499
- { name: 'Session', value: 'session' },
500
- { name: 'User', value: 'user' },
501
- { name: 'Agent', value: 'agent' },
502
- { name: 'Global', value: 'global' },
503
- ],
504
- default: 'user',
505
- description: 'Filter by scope.',
506
- },
507
- {
508
- displayName: 'Session ID',
509
- name: 'sessionId',
510
- type: 'string',
511
- default: '',
512
- description: 'Filter by session ID.',
513
- },
514
371
  {
515
372
  displayName: 'Tags',
516
373
  name: 'tags',
@@ -529,13 +386,6 @@ class Retainr {
529
386
  default: 0.35,
530
387
  description: 'Minimum similarity threshold (0-1).',
531
388
  },
532
- {
533
- displayName: 'User ID',
534
- name: 'userId',
535
- type: 'string',
536
- default: '',
537
- description: 'Filter by user ID.',
538
- },
539
389
  ],
540
390
  },
541
391
  // ==================================================================
@@ -547,8 +397,8 @@ class Retainr {
547
397
  type: 'string',
548
398
  required: false,
549
399
  default: '',
550
- placeholder: 'sarah-chen-001',
551
- description: 'Filter by customer/tenant namespace within this workspace.',
400
+ placeholder: 'customer:alice',
401
+ description: 'Group memories by any string. Leave empty for global scope.',
552
402
  displayOptions: {
553
403
  show: {
554
404
  resource: ['memory'],
@@ -569,13 +419,6 @@ class Retainr {
569
419
  },
570
420
  },
571
421
  options: [
572
- {
573
- displayName: 'Agent ID',
574
- name: 'agentId',
575
- type: 'string',
576
- default: '',
577
- description: 'Filter by agent ID.',
578
- },
579
422
  {
580
423
  displayName: 'Limit',
581
424
  name: 'limit',
@@ -596,26 +439,6 @@ class Retainr {
596
439
  default: 0,
597
440
  description: 'Pagination offset.',
598
441
  },
599
- {
600
- displayName: 'Scope',
601
- name: 'scope',
602
- type: 'options',
603
- options: [
604
- { name: 'Session', value: 'session' },
605
- { name: 'User', value: 'user' },
606
- { name: 'Agent', value: 'agent' },
607
- { name: 'Global', value: 'global' },
608
- ],
609
- default: 'user',
610
- description: 'Filter by scope.',
611
- },
612
- {
613
- displayName: 'Session ID',
614
- name: 'sessionId',
615
- type: 'string',
616
- default: '',
617
- description: 'Filter by session ID.',
618
- },
619
442
  {
620
443
  displayName: 'Tags',
621
444
  name: 'tags',
@@ -623,45 +446,19 @@ class Retainr {
623
446
  default: '',
624
447
  description: 'Comma-separated list of tags to filter by.',
625
448
  },
626
- {
627
- displayName: 'User ID',
628
- name: 'userId',
629
- type: 'string',
630
- default: '',
631
- description: 'Filter by user ID.',
632
- },
633
449
  ],
634
450
  },
635
451
  // ==================================================================
636
452
  // Fields — Memory > Delete
637
453
  // ==================================================================
638
- {
639
- displayName: 'Scope',
640
- name: 'deleteScope',
641
- type: 'options',
642
- default: 'session',
643
- description: 'Scope of memories to delete. At least one filter is required.',
644
- options: [
645
- { name: 'Session', value: 'session' },
646
- { name: 'User', value: 'user' },
647
- { name: 'Agent', value: 'agent' },
648
- { name: 'Global', value: 'global' },
649
- ],
650
- displayOptions: {
651
- show: {
652
- resource: ['memory'],
653
- operation: ['delete'],
654
- },
655
- },
656
- },
657
454
  {
658
455
  displayName: 'Namespace',
659
456
  name: 'namespace',
660
457
  type: 'string',
661
458
  required: false,
662
459
  default: '',
663
- placeholder: 'sarah-chen-001',
664
- description: 'Filter by customer/tenant namespace within this workspace.',
460
+ placeholder: 'customer:alice',
461
+ description: 'Group memories by any string. Leave empty for global scope.',
665
462
  displayOptions: {
666
463
  show: {
667
464
  resource: ['memory'],
@@ -669,42 +466,6 @@ class Retainr {
669
466
  },
670
467
  },
671
468
  },
672
- {
673
- displayName: 'Additional Fields',
674
- name: 'deleteAdditionalFields',
675
- type: 'collection',
676
- placeholder: 'Add Field',
677
- default: {},
678
- displayOptions: {
679
- show: {
680
- resource: ['memory'],
681
- operation: ['delete'],
682
- },
683
- },
684
- options: [
685
- {
686
- displayName: 'Agent ID',
687
- name: 'agentId',
688
- type: 'string',
689
- default: '',
690
- description: 'Filter by agent ID.',
691
- },
692
- {
693
- displayName: 'Session ID',
694
- name: 'sessionId',
695
- type: 'string',
696
- default: '',
697
- description: 'Filter by session ID.',
698
- },
699
- {
700
- displayName: 'User ID',
701
- name: 'userId',
702
- type: 'string',
703
- default: '',
704
- description: 'Filter by user ID.',
705
- },
706
- ],
707
- },
708
469
  ],
709
470
  };
710
471
  }
@@ -807,27 +568,12 @@ function parseTags(raw) {
807
568
  // ======================================================================
808
569
  async function storeMemory(i, baseUrl) {
809
570
  const content = this.getNodeParameter('content', i);
810
- const scope = this.getNodeParameter('scope', i);
811
571
  const namespace = this.getNodeParameter('namespace', i, '');
812
572
  const additional = this.getNodeParameter('storeAdditionalFields', i);
813
- const body = { content, scope };
814
- // Scope-specific IDs
815
- if (scope === 'session') {
816
- body.session_id = this.getNodeParameter('sessionId', i);
817
- }
818
- else if (scope === 'user') {
819
- body.user_id = this.getNodeParameter('userId', i);
820
- }
821
- else if (scope === 'agent') {
822
- body.agent_id = this.getNodeParameter('agentId', i);
823
- }
824
- // Namespace — top-level parameter takes priority, fall back to additional fields
573
+ const body = { content };
825
574
  if (namespace) {
826
575
  body.namespace = namespace;
827
576
  }
828
- else if (additional.namespace) {
829
- body.namespace = additional.namespace;
830
- }
831
577
  if (additional.tags)
832
578
  body.tags = parseTags(additional.tags);
833
579
  if (additional.ttlSeconds)
@@ -848,21 +594,9 @@ async function searchMemories(i, baseUrl) {
848
594
  const namespace = this.getNodeParameter('namespace', i, '');
849
595
  const additional = this.getNodeParameter('searchAdditionalFields', i);
850
596
  const body = { query };
851
- if (additional.scope)
852
- body.scope = additional.scope;
853
- if (additional.sessionId)
854
- body.session_id = additional.sessionId;
855
- if (additional.userId)
856
- body.user_id = additional.userId;
857
- if (additional.agentId)
858
- body.agent_id = additional.agentId;
859
- // Namespace — top-level parameter takes priority, fall back to additional fields
860
597
  if (namespace) {
861
598
  body.namespace = namespace;
862
599
  }
863
- else if (additional.namespace) {
864
- body.namespace = additional.namespace;
865
- }
866
600
  if (additional.tags)
867
601
  body.tags = parseTags(additional.tags);
868
602
  if (additional.limit)
@@ -877,21 +611,9 @@ async function getContext(i, baseUrl) {
877
611
  const namespace = this.getNodeParameter('namespace', i, '');
878
612
  const additional = this.getNodeParameter('contextAdditionalFields', i);
879
613
  const body = { query, format };
880
- if (additional.scope)
881
- body.scope = additional.scope;
882
- if (additional.sessionId)
883
- body.session_id = additional.sessionId;
884
- if (additional.userId)
885
- body.user_id = additional.userId;
886
- if (additional.agentId)
887
- body.agent_id = additional.agentId;
888
- // Namespace — top-level parameter takes priority, fall back to additional fields
889
614
  if (namespace) {
890
615
  body.namespace = namespace;
891
616
  }
892
- else if (additional.namespace) {
893
- body.namespace = additional.namespace;
894
- }
895
617
  if (additional.tags)
896
618
  body.tags = parseTags(additional.tags);
897
619
  if (additional.maxMemories)
@@ -904,21 +626,9 @@ async function listMemories(i, baseUrl) {
904
626
  const namespace = this.getNodeParameter('namespace', i, '');
905
627
  const additional = this.getNodeParameter('listAdditionalFields', i);
906
628
  const qs = {};
907
- if (additional.scope)
908
- qs.scope = additional.scope;
909
- if (additional.sessionId)
910
- qs.session_id = additional.sessionId;
911
- if (additional.userId)
912
- qs.user_id = additional.userId;
913
- if (additional.agentId)
914
- qs.agent_id = additional.agentId;
915
- // Namespace — top-level parameter takes priority, fall back to additional fields
916
629
  if (namespace) {
917
630
  qs.namespace = namespace;
918
631
  }
919
- else if (additional.namespace) {
920
- qs.namespace = additional.namespace;
921
- }
922
632
  if (additional.tags)
923
633
  qs.tags = additional.tags;
924
634
  if (additional.limit)
@@ -928,23 +638,11 @@ async function listMemories(i, baseUrl) {
928
638
  return apiRequest.call(this, 'GET', baseUrl, '/v1/memories', undefined, qs);
929
639
  }
930
640
  async function deleteMemories(i, baseUrl) {
931
- const scope = this.getNodeParameter('deleteScope', i);
932
641
  const namespace = this.getNodeParameter('namespace', i, '');
933
- const additional = this.getNodeParameter('deleteAdditionalFields', i);
934
- const body = { scope };
935
- if (additional.sessionId)
936
- body.session_id = additional.sessionId;
937
- if (additional.userId)
938
- body.user_id = additional.userId;
939
- if (additional.agentId)
940
- body.agent_id = additional.agentId;
941
- // Namespace — top-level parameter takes priority, fall back to additional fields
642
+ const body = {};
942
643
  if (namespace) {
943
644
  body.namespace = namespace;
944
645
  }
945
- else if (additional.namespace) {
946
- body.namespace = additional.namespace;
947
- }
948
646
  return apiRequest.call(this, 'DELETE', baseUrl, '/v1/memories', body);
949
647
  }
950
648
  async function getWorkspaceInfo(baseUrl) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stackflo-labs/n8n-nodes-retainr",
3
- "version": "0.3.1",
3
+ "version": "0.4.1",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/stackflo-labs/n8n-nodes-retainr.git"