@memoryrelay/plugin-memoryrelay-ai 0.2.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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Alteriom
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,465 @@
1
+ # OpenClaw Plugin for MemoryRelay AI
2
+
3
+ [![npm version](https://img.shields.io/npm/v/@memoryrelay/plugin-memoryrelay-ai)](https://www.npmjs.com/package/@memoryrelay/plugin-memoryrelay-ai)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
5
+
6
+ Long-term memory plugin for OpenClaw agents using [MemoryRelay API](https://api.memoryrelay.net).
7
+
8
+ ## Features
9
+
10
+ - 🧠 **Semantic Search** — Natural language memory retrieval with vector embeddings
11
+ - 🔄 **Auto-Recall** — Automatically inject relevant memories into agent context
12
+ - 📝 **Auto-Capture** — Intelligently detect and store important information
13
+ - 🤖 **Multi-Agent** — Isolated memory namespaces per agent
14
+ - 🛠️ **CLI Tools** — Manage memories via `openclaw memoryrelay` commands
15
+ - 🔌 **Tool Integration** — Three memory tools for AI agents
16
+
17
+ ## Installation
18
+
19
+ ```bash
20
+ openclaw plugins install @memoryrelay/plugin-memoryrelay-ai
21
+ ```
22
+
23
+ Or via npm:
24
+
25
+ ```bash
26
+ npm install -g @memoryrelay/plugin-memoryrelay-ai
27
+ ```
28
+
29
+ ## Quick Start
30
+
31
+ ### 1. Get API Key
32
+
33
+ Sign up at [memoryrelay.io](https://memoryrelay.io) or use the public demo API.
34
+
35
+ ### 2. Configure
36
+
37
+ Add to your `~/.openclaw/openclaw.json`:
38
+
39
+ ```json
40
+ {
41
+ "plugins": {
42
+ "slots": {
43
+ "memory": "plugin-memoryrelay-ai"
44
+ },
45
+ "entries": {
46
+ "plugin-memoryrelay-ai": {
47
+ "enabled": true,
48
+ "config": {
49
+ "apiKey": "mem_prod_...",
50
+ "agentId": "my-agent",
51
+ "apiUrl": "https://api.memoryrelay.net",
52
+ "autoRecall": true,
53
+ "autoCapture": false
54
+ }
55
+ }
56
+ }
57
+ }
58
+ }
59
+ ```
60
+
61
+ ### 3. Restart Gateway
62
+
63
+ ```bash
64
+ openclaw gateway restart
65
+ ```
66
+
67
+ ### 4. Test Connection
68
+
69
+ ```bash
70
+ openclaw memoryrelay status
71
+ ```
72
+
73
+ ## Usage
74
+
75
+ ### AI Agent Tools
76
+
77
+ The plugin provides three tools your AI agent can use:
78
+
79
+ #### `memory_store`
80
+
81
+ Store a new memory with optional metadata.
82
+
83
+ > **Note**: The `agent_id` parameter is automatically injected from your config. You don't need to include it.
84
+
85
+ **Parameters:**
86
+ - `content` (string, required) - Memory content (1-50,000 characters)
87
+ - `metadata` (object, optional) - Key-value metadata (max 10KB when serialized)
88
+
89
+ **Example:**
90
+ ```typescript
91
+ memory_store({
92
+ content: "User prefers concise bullet-point responses",
93
+ metadata: { category: "preferences", importance: "high" }
94
+ })
95
+ ```
96
+
97
+ **Returns:** Memory object with `id`, `content`, `agent_id`, `metadata`, `created_at`, `updated_at`
98
+
99
+ **Rate Limit**: 30 requests per minute
100
+
101
+ #### `memory_recall`
102
+
103
+ Search memories using semantic similarity.
104
+
105
+ **Parameters:**
106
+ - `query` (string, required) - Natural language search query
107
+ - `limit` (number, optional, default: 10) - Maximum results (1-50)
108
+ - `threshold` (number, optional, default: 0.5) - Minimum similarity score (0-1)
109
+
110
+ **Example:**
111
+ ```typescript
112
+ memory_recall({
113
+ query: "user communication preferences",
114
+ limit: 5,
115
+ threshold: 0.7
116
+ })
117
+ ```
118
+
119
+ **Returns:** Array of search results with `memory` object and `score` (0-1):
120
+ ```json
121
+ {
122
+ "results": [
123
+ {
124
+ "memory": {
125
+ "id": "550e8400-...",
126
+ "content": "User prefers concise bullet-point responses",
127
+ "metadata": { "category": "preferences" },
128
+ "created_at": 1707649200
129
+ },
130
+ "score": 0.89
131
+ }
132
+ ]
133
+ }
134
+ ```
135
+
136
+ #### `memory_forget`
137
+
138
+ Delete a memory by ID or search query.
139
+
140
+ **Parameters:**
141
+ - `memoryId` (string, optional) - Memory UUID to delete
142
+ - `query` (string, optional) - Search query (shows candidates if multiple matches)
143
+
144
+ **Examples:**
145
+ ```typescript
146
+ // By ID
147
+ memory_forget({ memoryId: "550e8400-..." })
148
+
149
+ // By query (interactive if multiple matches)
150
+ memory_forget({ query: "outdated preference" })
151
+ ```
152
+
153
+ **Returns:** Success confirmation
154
+
155
+ ### CLI Commands
156
+
157
+ ```bash
158
+ # Check status
159
+ openclaw memoryrelay status
160
+
161
+ # List recent memories
162
+ openclaw memoryrelay list --limit 10
163
+
164
+ # Search memories
165
+ openclaw memoryrelay search "API configuration"
166
+ ```
167
+
168
+ ### Auto-Recall
169
+
170
+ When `autoRecall: true`, relevant memories are automatically injected before each agent turn:
171
+
172
+ ```xml
173
+ <relevant-memories>
174
+ The following memories from MemoryRelay may be relevant:
175
+ - User prefers concise responses
176
+ - Project uses TypeScript with strict mode
177
+ - ...
178
+ </relevant-memories>
179
+ ```
180
+
181
+ **Config:**
182
+ - `recallLimit`: Max memories (default: 5)
183
+ - `recallThreshold`: Min similarity score (default: 0.3)
184
+
185
+ ### Auto-Capture
186
+
187
+ When `autoCapture: true`, the plugin detects and stores important information automatically.
188
+
189
+ **Patterns detected:**
190
+ - "remember that..."
191
+ - "my name/email/phone is..."
192
+ - "important: ..."
193
+ - API keys, SSH configs, preferences
194
+
195
+ **Note:** Disabled by default for privacy.
196
+
197
+ ## Configuration
198
+
199
+ | Field | Type | Required | Default | Description |
200
+ |-------|------|----------|---------|-------------|
201
+ | `apiKey` | string | ✅ | - | MemoryRelay API key |
202
+ | `agentId` | string | ✅ | - | Unique agent identifier |
203
+ | `apiUrl` | string | No | `api.memoryrelay.net` | API endpoint |
204
+ | `autoRecall` | boolean | No | `true` | Auto-inject memories |
205
+ | `autoCapture` | boolean | No | `false` | Auto-store information |
206
+ | `recallLimit` | number | No | `5` | Max memories to inject |
207
+ | `recallThreshold` | number | No | `0.3` | Similarity threshold (0-1) |
208
+
209
+ ### Environment Variables
210
+
211
+ Alternatively, use environment variables:
212
+
213
+ ```bash
214
+ export MEMORYRELAY_API_KEY="mem_prod_..."
215
+ export MEMORYRELAY_AGENT_ID="my-agent"
216
+ ```
217
+
218
+ Then reference in config:
219
+ ```json
220
+ {
221
+ "apiKey": "${MEMORYRELAY_API_KEY}",
222
+ "agentId": "${MEMORYRELAY_AGENT_ID}"
223
+ }
224
+ ```
225
+
226
+ ## Architecture
227
+
228
+ ```
229
+ ┌─────────────────────┐
230
+ │ OpenClaw Agent │
231
+ │ (Your AI) │
232
+ └──────────┬──────────┘
233
+
234
+ │ Plugin API
235
+
236
+ ┌─────────────────────┐
237
+ │ @memoryrelay/ │
238
+ │ openclaw-plugin │
239
+ │ - Tools │
240
+ │ - CLI │
241
+ │ - Lifecycle Hooks │
242
+ └──────────┬──────────┘
243
+
244
+ │ HTTPS REST
245
+
246
+ ┌─────────────────────┐
247
+ │ MemoryRelay API │
248
+ │ api.memoryrelay.net │
249
+ └─────────────────────┘
250
+ ```
251
+
252
+ ## API
253
+
254
+ The plugin includes a TypeScript client for MemoryRelay API:
255
+
256
+ ```typescript
257
+ class MemoryRelayClient {
258
+ async store(content: string, metadata?: Record<string, string>): Promise<Memory>
259
+ async search(query: string, limit?: number, threshold?: number): Promise<SearchResult[]>
260
+ async list(limit?: number, offset?: number): Promise<Memory[]>
261
+ async get(id: string): Promise<Memory>
262
+ async delete(id: string): Promise<void>
263
+ async health(): Promise<{ status: string }>
264
+ }
265
+ ```
266
+
267
+ ## Examples
268
+
269
+ ### Basic Usage
270
+
271
+ ```javascript
272
+ // Agent conversation:
273
+ // User: "Remember that I prefer TypeScript over JavaScript"
274
+ // Agent uses: memory_store({ content: "User prefers TypeScript over JavaScript" })
275
+
276
+ // Later:
277
+ // User: "What language should we use?"
278
+ // Agent uses: memory_recall({ query: "programming language preference" })
279
+ // → Finds previous preference and suggests TypeScript
280
+ ```
281
+
282
+ ### CLI Workflow
283
+
284
+ ```bash
285
+ # Store memory
286
+ openclaw memoryrelay store "Project uses Kubernetes on AWS EKS"
287
+
288
+ # Search later
289
+ openclaw memoryrelay search "kubernetes setup"
290
+ # → Returns relevant infrastructure memories
291
+
292
+ # List all
293
+ openclaw memoryrelay list --limit 20
294
+
295
+ # Delete old memory
296
+ openclaw memoryrelay forget --id abc123
297
+ ```
298
+
299
+ ## Troubleshooting
300
+
301
+ ### Plugin Not Loading
302
+
303
+ ```bash
304
+ # Check plugin status
305
+ openclaw plugins list | grep memoryrelay
306
+
307
+ # View config validation
308
+ openclaw doctor
309
+
310
+ # Check logs
311
+ journalctl -u openclaw-gateway -f
312
+ ```
313
+
314
+ ### Connection Failed
315
+
316
+ ```bash
317
+ # Test API directly
318
+ curl https://api.memoryrelay.net/v1/health
319
+
320
+ # Check API key
321
+ openclaw memoryrelay status
322
+ ```
323
+
324
+ ### No Memories Returned
325
+
326
+ - Check `recallThreshold` (lower = more results)
327
+ - Verify `agentId` matches your API agent
328
+ - Try broader search queries
329
+
330
+ ## Security
331
+
332
+ - API keys stored in `openclaw.json` (not committed to git)
333
+ - Supports environment variable substitution
334
+ - Auto-capture disabled by default (privacy)
335
+ - No hardcoded credentials
336
+
337
+ **Best Practices:**
338
+ - Use environment variables in production
339
+ - Never commit `openclaw.json` with real keys
340
+ - Rotate API keys regularly
341
+ - Review auto-captured memories periodically
342
+
343
+ ## Development
344
+
345
+ ### File Structure
346
+
347
+ ```
348
+ openclaw-plugin/
349
+ ├── index.ts # Plugin implementation
350
+ ├── openclaw.plugin.json # Plugin manifest
351
+ ├── package.json # NPM metadata
352
+ ├── LICENSE # MIT license
353
+ └── README.md # This file
354
+ ```
355
+
356
+ ### Testing
357
+
358
+ ```bash
359
+ # Install locally
360
+ openclaw plugins install --link .
361
+
362
+ # Test tools
363
+ # (via agent conversation or CLI)
364
+
365
+ # View logs
366
+ tail -f ~/.openclaw/logs/gateway.log
367
+ ```
368
+
369
+ ## Related Projects
370
+
371
+ - **MemoryRelay API** — REST API backend (FastAPI + PostgreSQL)
372
+ - **MCP Server** — [`memoryrelay-mcp-server`](https://www.npmjs.com/package/memoryrelay-mcp-server) for Claude Desktop
373
+ - **Python SDK** — `memoryrelay` on PyPI (coming soon)
374
+
375
+ ## Contributing
376
+
377
+ Contributions welcome! Please:
378
+
379
+ 1. Fork the repository
380
+ 2. Create a feature branch
381
+ 3. Add tests (if applicable)
382
+ 4. Update documentation
383
+ 5. Submit a pull request
384
+
385
+ ## Support
386
+
387
+ - **Issues**: [GitHub Issues](https://github.com/memoryrelay/openclaw-plugin/issues)
388
+ - **Docs**: [memoryrelay.io](https://memoryrelay.io)
389
+ - **Discord**: [OpenClaw Community](https://discord.gg/clawd)
390
+
391
+ ## License
392
+
393
+ MIT © 2026 MemoryRelay
394
+
395
+ ---
396
+
397
+ ## Changelog
398
+
399
+ ### v0.2.0 (2026-02-13) - BREAKING CHANGE
400
+
401
+ **Package Renamed to Fix Warnings:**
402
+ - Old: `@memoryrelay/openclaw-plugin`
403
+ - New: `@memoryrelay/plugin-memoryrelay-ai`
404
+ - Plugin ID remains: `plugin-memoryrelay-ai`
405
+ - **Why**: Package name must match plugin ID to avoid config warnings
406
+ - **Impact**: No code changes, just package name alignment
407
+
408
+ **Migration**:
409
+ ```bash
410
+ # Uninstall old package
411
+ openclaw plugins uninstall @memoryrelay/openclaw-plugin
412
+ # Install new package
413
+ openclaw plugins install @memoryrelay/plugin-memoryrelay-ai
414
+ ```
415
+
416
+ ### v0.1.2 (2026-02-13)
417
+
418
+ **Bug Fix:**
419
+ - Fixed installation directory mismatch by adding `openclaw.id` to package.json
420
+ - Plugin now correctly installs to `plugin-memoryrelay-ai` directory
421
+ - Resolves "plugin not found" error during `openclaw plugins install`
422
+
423
+ ### v0.1.1 (2026-02-13)
424
+
425
+ **Breaking Changes:**
426
+ - Plugin ID changed: `memory-memoryrelay` → `plugin-memoryrelay-ai`
427
+ - Update your `openclaw.json` to use new ID in `plugins.slots.memory` and `plugins.entries`
428
+
429
+ **Documentation Improvements:**
430
+ - ✅ Added agent_id auto-injection documentation
431
+ - ✅ Added size limits (content 1-50K chars, metadata 10KB)
432
+ - ✅ Added rate limit info (30 req/min)
433
+ - ✅ Enhanced tool documentation with return formats
434
+ - ✅ Added response format examples for memory_recall
435
+
436
+ **Migration Guide:**
437
+ ```json
438
+ {
439
+ "plugins": {
440
+ "slots": {
441
+ "memory": "plugin-memoryrelay-ai" // Changed
442
+ },
443
+ "entries": {
444
+ "plugin-memoryrelay-ai": { // Changed
445
+ "enabled": true,
446
+ "config": { ... }
447
+ }
448
+ }
449
+ }
450
+ }
451
+ ```
452
+
453
+ ### v0.1.0 (2026-02-12)
454
+
455
+ - Initial release
456
+ - Three tools: memory_store, memory_recall, memory_forget
457
+ - Auto-recall and auto-capture features
458
+ - CLI commands
459
+ - Production deployment on 3 agents
460
+
461
+ ---
462
+
463
+ **Homepage**: https://memoryrelay.io
464
+ **API**: https://api.memoryrelay.net
465
+ **Source**: https://github.com/memoryrelay/openclaw-plugin
package/index.ts ADDED
@@ -0,0 +1,519 @@
1
+ /**
2
+ * OpenClaw Memory Plugin - MemoryRelay
3
+ *
4
+ * Long-term memory with vector search using MemoryRelay API.
5
+ * Provides auto-recall and auto-capture via lifecycle hooks.
6
+ *
7
+ * API: https://api.memoryrelay.net
8
+ * Docs: https://memoryrelay.io
9
+ */
10
+
11
+ import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
12
+
13
+ // ============================================================================
14
+ // Types
15
+ // ============================================================================
16
+
17
+ interface MemoryRelayConfig {
18
+ apiKey: string;
19
+ agentId: string;
20
+ apiUrl?: string;
21
+ autoCapture?: boolean;
22
+ autoRecall?: boolean;
23
+ recallLimit?: number;
24
+ recallThreshold?: number;
25
+ }
26
+
27
+ interface Memory {
28
+ id: string;
29
+ content: string;
30
+ agent_id: string;
31
+ user_id: string;
32
+ metadata: Record<string, string>;
33
+ entities: string[];
34
+ created_at: number;
35
+ updated_at: number;
36
+ }
37
+
38
+ interface SearchResult {
39
+ memory: Memory;
40
+ score: number;
41
+ }
42
+
43
+ // ============================================================================
44
+ // MemoryRelay API Client
45
+ // ============================================================================
46
+
47
+ class MemoryRelayClient {
48
+ constructor(
49
+ private readonly apiKey: string,
50
+ private readonly agentId: string,
51
+ private readonly apiUrl: string = "https://api.memoryrelay.net",
52
+ ) {}
53
+
54
+ private async request<T>(
55
+ method: string,
56
+ path: string,
57
+ body?: unknown,
58
+ ): Promise<T> {
59
+ const url = `${this.apiUrl}${path}`;
60
+
61
+ const response = await fetch(url, {
62
+ method,
63
+ headers: {
64
+ "Content-Type": "application/json",
65
+ Authorization: `Bearer ${this.apiKey}`,
66
+ "User-Agent": "openclaw-memory-memoryrelay/0.1.0",
67
+ },
68
+ body: body ? JSON.stringify(body) : undefined,
69
+ });
70
+
71
+ if (!response.ok) {
72
+ const errorData = await response.json().catch(() => ({}));
73
+ throw new Error(
74
+ `MemoryRelay API error: ${response.status} ${response.statusText}` +
75
+ (errorData.message ? ` - ${errorData.message}` : ""),
76
+ );
77
+ }
78
+
79
+ return response.json();
80
+ }
81
+
82
+ async store(content: string, metadata?: Record<string, string>): Promise<Memory> {
83
+ return this.request<Memory>("POST", "/v1/memories/memories", {
84
+ content,
85
+ metadata,
86
+ agent_id: this.agentId,
87
+ });
88
+ }
89
+
90
+ async search(
91
+ query: string,
92
+ limit: number = 5,
93
+ threshold: number = 0.3,
94
+ ): Promise<SearchResult[]> {
95
+ const response = await this.request<{ data: SearchResult[] }>(
96
+ "POST",
97
+ "/v1/memories/memories/search",
98
+ {
99
+ query,
100
+ limit,
101
+ threshold,
102
+ agent_id: this.agentId,
103
+ },
104
+ );
105
+ return response.data || [];
106
+ }
107
+
108
+ async list(limit: number = 20, offset: number = 0): Promise<Memory[]> {
109
+ const response = await this.request<{ data: Memory[] }>(
110
+ "GET",
111
+ `/v1/memories/memories?limit=${limit}&offset=${offset}`,
112
+ );
113
+ return response.data || [];
114
+ }
115
+
116
+ async get(id: string): Promise<Memory> {
117
+ return this.request<Memory>("GET", `/v1/memories/memories/${id}`);
118
+ }
119
+
120
+ async delete(id: string): Promise<void> {
121
+ await this.request<void>("DELETE", `/v1/memories/memories/${id}`);
122
+ }
123
+
124
+ async health(): Promise<{ status: string }> {
125
+ return this.request<{ status: string }>("GET", "/v1/health");
126
+ }
127
+ }
128
+
129
+ // ============================================================================
130
+ // Pattern Detection (for auto-capture)
131
+ // ============================================================================
132
+
133
+ const CAPTURE_PATTERNS = [
134
+ /remember\s+(?:that\s+)?/i,
135
+ /(?:my|the)\s+(?:name|email|phone|address|preference)/i,
136
+ /important(?:ly)?[:\s]/i,
137
+ /always\s+(?:use|prefer|want)/i,
138
+ /(?:do|don't)\s+(?:like|want|prefer)/i,
139
+ /(?:api|key|token|password|secret)(?:\s+is)?[:\s]/i,
140
+ /(?:ssh|server|host|ip|port)(?:\s+is)?[:\s]/i,
141
+ ];
142
+
143
+ function shouldCapture(text: string): boolean {
144
+ if (text.length < 20 || text.length > 2000) {
145
+ return false;
146
+ }
147
+ return CAPTURE_PATTERNS.some((pattern) => pattern.test(text));
148
+ }
149
+
150
+ // ============================================================================
151
+ // Plugin Export
152
+ // ============================================================================
153
+
154
+ export default async function plugin(api: OpenClawPluginApi): Promise<void> {
155
+ const cfg = api.pluginConfig as MemoryRelayConfig | undefined;
156
+ if (!cfg?.apiKey || !cfg?.agentId) {
157
+ api.logger.warn("memory-memoryrelay: missing apiKey or agentId, plugin disabled");
158
+ return;
159
+ }
160
+
161
+ const client = new MemoryRelayClient(
162
+ cfg.apiKey,
163
+ cfg.agentId,
164
+ cfg.apiUrl || "https://api.memoryrelay.net",
165
+ );
166
+
167
+ // Verify connection on startup
168
+ try {
169
+ await client.health();
170
+ api.logger.info(`memory-memoryrelay: connected to ${cfg.apiUrl || "api.memoryrelay.net"}`);
171
+ } catch (err) {
172
+ api.logger.error(`memory-memoryrelay: health check failed: ${String(err)}`);
173
+ return;
174
+ }
175
+
176
+ // ========================================================================
177
+ // Tools (using JSON Schema directly)
178
+ // ========================================================================
179
+
180
+ // memory_store tool
181
+ api.registerTool(
182
+ {
183
+ name: "memory_store",
184
+ description:
185
+ "Store a new memory in MemoryRelay. Use this to save important information, facts, preferences, or context that should be remembered for future conversations.",
186
+ inputSchema: {
187
+ type: "object",
188
+ properties: {
189
+ content: {
190
+ type: "string",
191
+ description: "The memory content to store. Be specific and include relevant context.",
192
+ },
193
+ metadata: {
194
+ type: "object",
195
+ description: "Optional key-value metadata to attach to the memory",
196
+ additionalProperties: { type: "string" },
197
+ },
198
+ },
199
+ required: ["content"],
200
+ },
201
+ handler: async ({ content, metadata }: { content: string; metadata?: Record<string, string> }) => {
202
+ try {
203
+ const memory = await client.store(content, metadata);
204
+ return {
205
+ content: [
206
+ {
207
+ type: "text",
208
+ text: `Memory stored successfully (id: ${memory.id.slice(0, 8)}...)`,
209
+ },
210
+ ],
211
+ details: { id: memory.id, stored: true },
212
+ };
213
+ } catch (err) {
214
+ return {
215
+ content: [{ type: "text", text: `Failed to store memory: ${String(err)}` }],
216
+ details: { error: String(err) },
217
+ };
218
+ }
219
+ },
220
+ },
221
+ { name: "memory_store" },
222
+ );
223
+
224
+ // memory_recall tool (semantic search)
225
+ api.registerTool(
226
+ {
227
+ name: "memory_recall",
228
+ description:
229
+ "Search memories using natural language. Returns the most relevant memories based on semantic similarity.",
230
+ inputSchema: {
231
+ type: "object",
232
+ properties: {
233
+ query: {
234
+ type: "string",
235
+ description: "Natural language search query",
236
+ },
237
+ limit: {
238
+ type: "number",
239
+ description: "Maximum results (1-20)",
240
+ minimum: 1,
241
+ maximum: 20,
242
+ default: 5,
243
+ },
244
+ },
245
+ required: ["query"],
246
+ },
247
+ handler: async ({ query, limit = 5 }: { query: string; limit?: number }) => {
248
+ try {
249
+ const results = await client.search(query, limit, cfg.recallThreshold || 0.3);
250
+
251
+ if (results.length === 0) {
252
+ return {
253
+ content: [{ type: "text", text: "No relevant memories found." }],
254
+ details: { count: 0 },
255
+ };
256
+ }
257
+
258
+ const formatted = results
259
+ .map(
260
+ (r) =>
261
+ `- [${r.score.toFixed(2)}] ${r.memory.content.slice(0, 200)}${
262
+ r.memory.content.length > 200 ? "..." : ""
263
+ }`,
264
+ )
265
+ .join("\n");
266
+
267
+ return {
268
+ content: [
269
+ {
270
+ type: "text",
271
+ text: `Found ${results.length} relevant memories:\n${formatted}`,
272
+ },
273
+ ],
274
+ details: {
275
+ count: results.length,
276
+ memories: results.map((r) => ({
277
+ id: r.memory.id,
278
+ content: r.memory.content,
279
+ score: r.score,
280
+ })),
281
+ },
282
+ };
283
+ } catch (err) {
284
+ return {
285
+ content: [{ type: "text", text: `Search failed: ${String(err)}` }],
286
+ details: { error: String(err) },
287
+ };
288
+ }
289
+ },
290
+ },
291
+ { name: "memory_recall" },
292
+ );
293
+
294
+ // memory_forget tool
295
+ api.registerTool(
296
+ {
297
+ name: "memory_forget",
298
+ description: "Delete a memory by ID or search for memories to forget.",
299
+ inputSchema: {
300
+ type: "object",
301
+ properties: {
302
+ memoryId: {
303
+ type: "string",
304
+ description: "Memory ID to delete",
305
+ },
306
+ query: {
307
+ type: "string",
308
+ description: "Search query to find memory",
309
+ },
310
+ },
311
+ },
312
+ handler: async ({ memoryId, query }: { memoryId?: string; query?: string }) => {
313
+ if (memoryId) {
314
+ try {
315
+ await client.delete(memoryId);
316
+ return {
317
+ content: [{ type: "text", text: `Memory ${memoryId.slice(0, 8)}... deleted.` }],
318
+ details: { action: "deleted", id: memoryId },
319
+ };
320
+ } catch (err) {
321
+ return {
322
+ content: [{ type: "text", text: `Delete failed: ${String(err)}` }],
323
+ details: { error: String(err) },
324
+ };
325
+ }
326
+ }
327
+
328
+ if (query) {
329
+ const results = await client.search(query, 5, 0.5);
330
+
331
+ if (results.length === 0) {
332
+ return {
333
+ content: [{ type: "text", text: "No matching memories found." }],
334
+ details: { count: 0 },
335
+ };
336
+ }
337
+
338
+ // If single high-confidence match, delete it
339
+ if (results.length === 1 && results[0].score > 0.9) {
340
+ await client.delete(results[0].memory.id);
341
+ return {
342
+ content: [
343
+ { type: "text", text: `Forgotten: "${results[0].memory.content.slice(0, 60)}..."` },
344
+ ],
345
+ details: { action: "deleted", id: results[0].memory.id },
346
+ };
347
+ }
348
+
349
+ const list = results
350
+ .map((r) => `- [${r.memory.id.slice(0, 8)}] ${r.memory.content.slice(0, 60)}...`)
351
+ .join("\n");
352
+
353
+ return {
354
+ content: [
355
+ {
356
+ type: "text",
357
+ text: `Found ${results.length} candidates. Specify memoryId:\n${list}`,
358
+ },
359
+ ],
360
+ details: { action: "candidates", count: results.length },
361
+ };
362
+ }
363
+
364
+ return {
365
+ content: [{ type: "text", text: "Provide query or memoryId." }],
366
+ details: { error: "missing_param" },
367
+ };
368
+ },
369
+ },
370
+ { name: "memory_forget" },
371
+ );
372
+
373
+ // ========================================================================
374
+ // CLI Commands
375
+ // ========================================================================
376
+
377
+ api.registerCli(
378
+ ({ program }) => {
379
+ const mem = program.command("memoryrelay").description("MemoryRelay memory plugin commands");
380
+
381
+ mem
382
+ .command("status")
383
+ .description("Check MemoryRelay connection status")
384
+ .action(async () => {
385
+ try {
386
+ const health = await client.health();
387
+ console.log(`Status: ${health.status}`);
388
+ console.log(`Agent ID: ${cfg.agentId}`);
389
+ console.log(`API: ${cfg.apiUrl || "https://api.memoryrelay.net"}`);
390
+ } catch (err) {
391
+ console.error(`Connection failed: ${String(err)}`);
392
+ }
393
+ });
394
+
395
+ mem
396
+ .command("list")
397
+ .description("List recent memories")
398
+ .option("--limit <n>", "Max results", "10")
399
+ .action(async (opts) => {
400
+ const memories = await client.list(parseInt(opts.limit));
401
+ for (const m of memories) {
402
+ console.log(`[${m.id.slice(0, 8)}] ${m.content.slice(0, 80)}...`);
403
+ }
404
+ console.log(`\nTotal: ${memories.length} memories`);
405
+ });
406
+
407
+ mem
408
+ .command("search")
409
+ .description("Search memories")
410
+ .argument("<query>", "Search query")
411
+ .option("--limit <n>", "Max results", "5")
412
+ .action(async (query, opts) => {
413
+ const results = await client.search(query, parseInt(opts.limit));
414
+ for (const r of results) {
415
+ console.log(`[${r.score.toFixed(2)}] ${r.memory.content.slice(0, 80)}...`);
416
+ }
417
+ });
418
+ },
419
+ { commands: ["memoryrelay"] },
420
+ );
421
+
422
+ // ========================================================================
423
+ // Lifecycle Hooks
424
+ // ========================================================================
425
+
426
+ // Auto-recall: inject relevant memories before agent starts
427
+ if (cfg.autoRecall) {
428
+ api.on("before_agent_start", async (event) => {
429
+ if (!event.prompt || event.prompt.length < 10) {
430
+ return;
431
+ }
432
+
433
+ try {
434
+ const results = await client.search(
435
+ event.prompt,
436
+ cfg.recallLimit || 5,
437
+ cfg.recallThreshold || 0.3,
438
+ );
439
+
440
+ if (results.length === 0) {
441
+ return;
442
+ }
443
+
444
+ const memoryContext = results
445
+ .map((r) => `- ${r.memory.content}`)
446
+ .join("\n");
447
+
448
+ api.logger.info?.(
449
+ `memory-memoryrelay: injecting ${results.length} memories into context`,
450
+ );
451
+
452
+ return {
453
+ prependContext: `<relevant-memories>\nThe following memories from MemoryRelay may be relevant:\n${memoryContext}\n</relevant-memories>`,
454
+ };
455
+ } catch (err) {
456
+ api.logger.warn?.(`memory-memoryrelay: recall failed: ${String(err)}`);
457
+ }
458
+ });
459
+ }
460
+
461
+ // Auto-capture: analyze and store important information after agent ends
462
+ if (cfg.autoCapture) {
463
+ api.on("agent_end", async (event) => {
464
+ if (!event.success || !event.messages || event.messages.length === 0) {
465
+ return;
466
+ }
467
+
468
+ try {
469
+ const texts: string[] = [];
470
+ for (const msg of event.messages) {
471
+ if (!msg || typeof msg !== "object") continue;
472
+ const msgObj = msg as Record<string, unknown>;
473
+ const role = msgObj.role;
474
+ if (role !== "user" && role !== "assistant") continue;
475
+
476
+ const content = msgObj.content;
477
+ if (typeof content === "string") {
478
+ texts.push(content);
479
+ } else if (Array.isArray(content)) {
480
+ for (const block of content) {
481
+ if (
482
+ block &&
483
+ typeof block === "object" &&
484
+ "type" in block &&
485
+ (block as Record<string, unknown>).type === "text" &&
486
+ "text" in block
487
+ ) {
488
+ texts.push((block as Record<string, unknown>).text as string);
489
+ }
490
+ }
491
+ }
492
+ }
493
+
494
+ const toCapture = texts.filter((text) => text && shouldCapture(text));
495
+ if (toCapture.length === 0) return;
496
+
497
+ let stored = 0;
498
+ for (const text of toCapture.slice(0, 3)) {
499
+ // Check for duplicates via search
500
+ const existing = await client.search(text, 1, 0.95);
501
+ if (existing.length > 0) continue;
502
+
503
+ await client.store(text, { source: "auto-capture" });
504
+ stored++;
505
+ }
506
+
507
+ if (stored > 0) {
508
+ api.logger.info?.(`memory-memoryrelay: auto-captured ${stored} memories`);
509
+ }
510
+ } catch (err) {
511
+ api.logger.warn?.(`memory-memoryrelay: capture failed: ${String(err)}`);
512
+ }
513
+ });
514
+ }
515
+
516
+ api.logger.info?.(
517
+ `memory-memoryrelay: plugin loaded (autoRecall: ${cfg.autoRecall}, autoCapture: ${cfg.autoCapture})`,
518
+ );
519
+ }
@@ -0,0 +1,67 @@
1
+ {
2
+ "id": "plugin-memoryrelay-ai",
3
+ "kind": "memory",
4
+ "name": "MemoryRelay AI",
5
+ "description": "AI memory service using MemoryRelay API (api.memoryrelay.net)",
6
+ "version": "0.1.0",
7
+ "uiHints": {
8
+ "apiKey": {
9
+ "label": "MemoryRelay API Key",
10
+ "sensitive": true,
11
+ "placeholder": "mem_prod_...",
12
+ "help": "API key from memoryrelay.io (or use ${MEMORYRELAY_API_KEY})"
13
+ },
14
+ "agentId": {
15
+ "label": "Agent ID",
16
+ "placeholder": "iris",
17
+ "help": "Unique identifier for this agent in MemoryRelay"
18
+ },
19
+ "apiUrl": {
20
+ "label": "API URL",
21
+ "placeholder": "https://api.memoryrelay.net",
22
+ "advanced": true,
23
+ "help": "MemoryRelay API endpoint"
24
+ },
25
+ "autoCapture": {
26
+ "label": "Auto-Capture",
27
+ "help": "Automatically capture important information from conversations"
28
+ },
29
+ "autoRecall": {
30
+ "label": "Auto-Recall",
31
+ "help": "Automatically inject relevant memories into context"
32
+ }
33
+ },
34
+ "configSchema": {
35
+ "type": "object",
36
+ "additionalProperties": false,
37
+ "properties": {
38
+ "apiKey": {
39
+ "type": "string"
40
+ },
41
+ "agentId": {
42
+ "type": "string"
43
+ },
44
+ "apiUrl": {
45
+ "type": "string",
46
+ "default": "https://api.memoryrelay.net"
47
+ },
48
+ "autoCapture": {
49
+ "type": "boolean",
50
+ "default": false
51
+ },
52
+ "autoRecall": {
53
+ "type": "boolean",
54
+ "default": true
55
+ },
56
+ "recallLimit": {
57
+ "type": "number",
58
+ "default": 5
59
+ },
60
+ "recallThreshold": {
61
+ "type": "number",
62
+ "default": 0.3
63
+ }
64
+ },
65
+ "required": ["apiKey", "agentId"]
66
+ }
67
+ }
package/package.json ADDED
@@ -0,0 +1,43 @@
1
+ {
2
+ "name": "@memoryrelay/plugin-memoryrelay-ai",
3
+ "version": "0.2.0",
4
+ "description": "OpenClaw memory plugin for MemoryRelay API - long-term memory with semantic search",
5
+ "type": "module",
6
+ "main": "index.ts",
7
+ "keywords": [
8
+ "openclaw",
9
+ "plugin",
10
+ "memory",
11
+ "memoryrelay",
12
+ "semantic-search",
13
+ "vector-search",
14
+ "ai-memory",
15
+ "agent-memory"
16
+ ],
17
+ "author": "MemoryRelay",
18
+ "license": "MIT",
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "https://github.com/memoryrelay/openclaw-plugin.git"
22
+ },
23
+ "bugs": {
24
+ "url": "https://github.com/memoryrelay/openclaw-plugin/issues"
25
+ },
26
+ "homepage": "https://github.com/memoryrelay/openclaw-plugin#readme",
27
+ "peerDependencies": {
28
+ "openclaw": ">=2026.2.0"
29
+ },
30
+ "openclaw": {
31
+ "id": "plugin-memoryrelay-ai",
32
+ "extensions": ["./"]
33
+ },
34
+ "files": [
35
+ "index.ts",
36
+ "openclaw.plugin.json",
37
+ "README.md",
38
+ "LICENSE"
39
+ ],
40
+ "engines": {
41
+ "node": ">=18.0.0"
42
+ }
43
+ }