@memoryrelay/plugin-memoryrelay-ai 0.13.0 → 0.14.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 +207 -197
- package/index.ts +491 -1
- package/openclaw.plugin.json +56 -4
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,125 +1,83 @@
|
|
|
1
|
-
# MemoryRelay AI
|
|
1
|
+
# MemoryRelay AI
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
[](https://openclaw.ai)
|
|
3
|
+
**Engineering Knowledge Platform for OpenClaw**
|
|
5
4
|
|
|
6
|
-
|
|
5
|
+
Persistent memory, architectural decisions, reusable patterns, and project orchestration for AI agents.
|
|
7
6
|
|
|
8
|
-
|
|
7
|
+
[](https://www.npmjs.com/package/@memoryrelay/plugin-memoryrelay-ai)
|
|
8
|
+
[](https://openclaw.ai)
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
- **8 Gateway Methods** for stats, debugging, and onboarding
|
|
12
|
-
- **Smart Auto-Capture** - Tier-based privacy system with automatic filtering
|
|
13
|
-
- **Daily Memory Stats** - Morning/evening summaries with growth metrics
|
|
14
|
-
- **Debug & Monitoring** - Comprehensive logging, health checks, and performance metrics
|
|
15
|
-
- **Semantic Search** - Vector-based retrieval finds relevant context by meaning
|
|
16
|
-
- **Auto-Recall** - Automatically injects relevant memories into agent context
|
|
17
|
-
- **Project-First Workflow** - Agents receive workflow instructions to start with project context
|
|
18
|
-
- **Decision Records** - Track and check architectural decisions before making new ones
|
|
19
|
-
- **Pattern Library** - Create, search, and adopt reusable conventions across projects
|
|
20
|
-
- **Session Tracking** - Track work sessions with summaries for continuity
|
|
21
|
-
- **External Session IDs** - Multi-agent collaboration and conversation-spanning sessions
|
|
22
|
-
- **Stale Session Cleanup** - Background service automatically closes inactive sessions after a configurable timeout
|
|
23
|
-
- **Sender Identity Tagging** - Multi-agent traceability via auto-injected `sender_id` metadata
|
|
24
|
-
- **Tool Group Filtering** - Enable only the tool groups you need
|
|
10
|
+
## Why MemoryRelay?
|
|
25
11
|
|
|
26
|
-
|
|
12
|
+
MemoryRelay is designed for engineering teams managing complex, long-running projects. It is not general-purpose Q&A memory.
|
|
27
13
|
|
|
28
|
-
|
|
14
|
+
| Feature | MemoryRelay | Mem0 | OpenClaw-Projects |
|
|
15
|
+
|---------|------------|------|-------------------|
|
|
16
|
+
| Semantic search | Yes (pgvector) | Yes | No |
|
|
17
|
+
| Sessions | Yes (auto-sync with OpenClaw sessions) | No | No |
|
|
18
|
+
| Architectural Decision Records | Yes (record, check, supersede) | No | No |
|
|
19
|
+
| Reusable patterns | Yes (create, adopt, suggest) | No | No |
|
|
20
|
+
| Project orchestration | Yes (10 tools, dependency graphs) | No | Basic |
|
|
21
|
+
| Entities / knowledge graph | Yes (create, link, graph) | Yes | No |
|
|
22
|
+
| Multi-agent collaboration | Yes (agent scoping, subagent tracking) | Limited | No |
|
|
23
|
+
| Auto-capture with privacy tiers | Yes (off/conservative/smart/aggressive) | Basic | No |
|
|
24
|
+
| Direct commands | 15 | ~5 | 0 |
|
|
25
|
+
| Lifecycle hooks | 13 | 0 | 0 |
|
|
26
|
+
| Tools | 39 | ~10 | 0 |
|
|
29
27
|
|
|
30
|
-
|
|
31
|
-
- Node.js >= 20.0.0
|
|
32
|
-
- MemoryRelay API key ([get one at memoryrelay.ai](https://memoryrelay.ai))
|
|
28
|
+
## Quick Start
|
|
33
29
|
|
|
34
|
-
|
|
30
|
+
**1. Install the plugin**
|
|
35
31
|
|
|
36
32
|
```bash
|
|
37
33
|
openclaw plugins install @memoryrelay/plugin-memoryrelay-ai
|
|
38
34
|
```
|
|
39
35
|
|
|
40
|
-
|
|
36
|
+
**2. Set your API key**
|
|
41
37
|
|
|
42
38
|
```bash
|
|
43
|
-
openclaw config set plugins.entries.plugin-memoryrelay-ai.config '{
|
|
44
|
-
"apiKey": "mem_prod_your_key_here",
|
|
45
|
-
"agentId": "your-agent-name",
|
|
46
|
-
"defaultProject": "my-project",
|
|
47
|
-
"autoRecall": true,
|
|
48
|
-
"autoCapture": false
|
|
49
|
-
}'
|
|
50
|
-
|
|
51
|
-
# Or use environment variables
|
|
52
39
|
export MEMORYRELAY_API_KEY="mem_prod_your_key_here"
|
|
53
|
-
export MEMORYRELAY_AGENT_ID="your-agent-name"
|
|
54
|
-
export MEMORYRELAY_DEFAULT_PROJECT="my-project"
|
|
55
|
-
|
|
56
|
-
# Restart gateway
|
|
57
|
-
openclaw gateway restart
|
|
58
40
|
```
|
|
59
41
|
|
|
60
|
-
|
|
42
|
+
Or configure inline:
|
|
61
43
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
| `agentId` | string | — | Unique agent identifier (or `MEMORYRELAY_AGENT_ID` env var, or agent name) |
|
|
66
|
-
| `apiUrl` | string | `https://api.memoryrelay.net` | API endpoint (or `MEMORYRELAY_API_URL` env var) |
|
|
67
|
-
| `defaultProject` | string | — | Default project slug applied to sessions, decisions, and memories |
|
|
68
|
-
| `enabledTools` | string | `all` | Comma-separated tool groups: `memory`, `entity`, `agent`, `session`, `decision`, `pattern`, `project`, `health` |
|
|
69
|
-
| `autoRecall` | boolean | `true` | Inject relevant memories into context each turn |
|
|
70
|
-
| `autoCapture` | boolean\|object | `false` | Auto-capture config. Boolean for backward compat, object for tier system: `{enabled, tier, confirmFirst}`. Tiers: `off`, `conservative`, `smart`, `aggressive`. |
|
|
71
|
-
| `recallLimit` | number | `5` | Max memories to inject per turn (1-20) |
|
|
72
|
-
| `recallThreshold` | number | `0.3` | Minimum similarity score for recall (0-1) |
|
|
73
|
-
| `excludeChannels` | string[] | `[]` | Channel IDs to skip auto-recall |
|
|
74
|
-
| `debug` | boolean | `false` | Enable debug logging of API calls |
|
|
75
|
-
| `verbose` | boolean | `false` | Include request/response bodies in debug logs |
|
|
76
|
-
| `logFile` | string | — | Optional file path for persistent debug logs |
|
|
77
|
-
| `maxLogEntries` | number | `100` | Circular buffer size for in-memory logs |
|
|
78
|
-
| `sessionTimeoutMinutes` | number | `120` | Idle time before a session is automatically closed by the cleanup service |
|
|
79
|
-
| `sessionCleanupIntervalMinutes` | number | `30` | How often the background cleanup service checks for stale sessions |
|
|
80
|
-
|
|
81
|
-
## Smart Auto-Capture
|
|
82
|
-
|
|
83
|
-
Four capture modes with built-in privacy protection:
|
|
84
|
-
|
|
85
|
-
| Tier | When to Use | Privacy Level |
|
|
86
|
-
|------|-------------|---------------|
|
|
87
|
-
| `off` | Manual storage only | N/A |
|
|
88
|
-
| `conservative` | Low-risk conversations only | High (blocks most patterns) |
|
|
89
|
-
| `smart` | Balanced automation | Medium (blocks sensitive data) |
|
|
90
|
-
| `aggressive` | Maximum capture | Low (minimal blocking) |
|
|
44
|
+
```bash
|
|
45
|
+
openclaw config set plugins.entries.plugin-memoryrelay-ai.config '{"apiKey": "mem_prod_..."}'
|
|
46
|
+
```
|
|
91
47
|
|
|
92
|
-
**
|
|
48
|
+
**3. Verify**
|
|
93
49
|
|
|
94
|
-
```
|
|
95
|
-
|
|
96
|
-
"autoCapture": {
|
|
97
|
-
"enabled": true,
|
|
98
|
-
"tier": "smart",
|
|
99
|
-
"confirmFirst": 5
|
|
100
|
-
}
|
|
101
|
-
}
|
|
50
|
+
```
|
|
51
|
+
/memory-health
|
|
102
52
|
```
|
|
103
53
|
|
|
104
|
-
|
|
54
|
+
Auto-recall and smart auto-capture are enabled by default. The plugin injects relevant memories into context every turn and captures important information automatically.
|
|
105
55
|
|
|
106
|
-
##
|
|
56
|
+
## Use Cases
|
|
107
57
|
|
|
108
|
-
|
|
58
|
+
**Tech Lead** managing 3+ projects:
|
|
59
|
+
- Record architectural decisions with `decision_record` so future agents (and teammates) check before re-deciding
|
|
60
|
+
- Create reusable patterns (`pattern_create`) and adopt them across projects
|
|
61
|
+
- Use `project_impact` to understand blast radius before cross-cutting changes
|
|
109
62
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
5. **Store findings** — `memory_store(content)` for important information
|
|
115
|
-
6. **Record decisions** — `decision_record(title, rationale)` for significant choices
|
|
116
|
-
7. **End session** — `session_end(session_id, summary)` with accomplishment summary
|
|
63
|
+
**DevOps Engineer**:
|
|
64
|
+
- Store infrastructure decisions as ADRs: "Why we chose Fargate over ECS on EC2"
|
|
65
|
+
- Capture runbooks and operational procedures as patterns
|
|
66
|
+
- Track dependencies between services with `project_add_relationship`
|
|
117
67
|
|
|
118
|
-
|
|
68
|
+
**Solo Developer**:
|
|
69
|
+
- Build a personal knowledge base of memories, entities, and decisions
|
|
70
|
+
- Use `memory_recall` for semantic search across everything you have stored
|
|
71
|
+
- Link entities to memories for a navigable knowledge graph
|
|
119
72
|
|
|
120
|
-
|
|
73
|
+
**Coding Agent**:
|
|
74
|
+
- Auto-capture learns from conversations without explicit tool calls
|
|
75
|
+
- Pattern adoption ensures consistent code style across sessions
|
|
76
|
+
- Session tracking provides continuity when context windows reset
|
|
121
77
|
|
|
122
|
-
|
|
78
|
+
## Features -- 39 Tools by Category
|
|
79
|
+
|
|
80
|
+
### Memory (9 tools) -- group: `memory`
|
|
123
81
|
|
|
124
82
|
| Tool | Description |
|
|
125
83
|
|------|-------------|
|
|
@@ -133,7 +91,7 @@ For new projects, the agent is guided to call `project_register()` first.
|
|
|
133
91
|
| `memory_context` | Build a token-budget-aware context window from relevant memories |
|
|
134
92
|
| `memory_promote` | Update a memory's importance score and tier |
|
|
135
93
|
|
|
136
|
-
### Entity
|
|
94
|
+
### Entity (4 tools) -- group: `entity`
|
|
137
95
|
|
|
138
96
|
| Tool | Description |
|
|
139
97
|
|------|-------------|
|
|
@@ -142,7 +100,7 @@ For new projects, the agent is guided to call `project_register()` first.
|
|
|
142
100
|
| `entity_list` | List entities with pagination |
|
|
143
101
|
| `entity_graph` | Explore an entity's neighborhood in the knowledge graph |
|
|
144
102
|
|
|
145
|
-
### Agent
|
|
103
|
+
### Agent (3 tools) -- group: `agent`
|
|
146
104
|
|
|
147
105
|
| Tool | Description |
|
|
148
106
|
|------|-------------|
|
|
@@ -150,7 +108,7 @@ For new projects, the agent is guided to call `project_register()` first.
|
|
|
150
108
|
| `agent_create` | Create a new agent (memory namespace) |
|
|
151
109
|
| `agent_get` | Get agent details by ID |
|
|
152
110
|
|
|
153
|
-
### Session
|
|
111
|
+
### Session (4 tools) -- group: `session`
|
|
154
112
|
|
|
155
113
|
| Tool | Description |
|
|
156
114
|
|------|-------------|
|
|
@@ -159,7 +117,7 @@ For new projects, the agent is guided to call `project_register()` first.
|
|
|
159
117
|
| `session_recall` | Get session details and timeline |
|
|
160
118
|
| `session_list` | List sessions filtered by project or status |
|
|
161
119
|
|
|
162
|
-
### Decision
|
|
120
|
+
### Decision (4 tools) -- group: `decision`
|
|
163
121
|
|
|
164
122
|
| Tool | Description |
|
|
165
123
|
|------|-------------|
|
|
@@ -168,7 +126,7 @@ For new projects, the agent is guided to call `project_register()` first.
|
|
|
168
126
|
| `decision_supersede` | Replace a decision with a new one (old is marked superseded) |
|
|
169
127
|
| `decision_check` | Semantic search for existing decisions before making new ones |
|
|
170
128
|
|
|
171
|
-
### Pattern
|
|
129
|
+
### Pattern (4 tools) -- group: `pattern`
|
|
172
130
|
|
|
173
131
|
| Tool | Description |
|
|
174
132
|
|------|-------------|
|
|
@@ -177,7 +135,7 @@ For new projects, the agent is guided to call `project_register()` first.
|
|
|
177
135
|
| `pattern_adopt` | Adopt an existing pattern for a project |
|
|
178
136
|
| `pattern_suggest` | Get pattern suggestions based on project stack |
|
|
179
137
|
|
|
180
|
-
### Project
|
|
138
|
+
### Project (10 tools) -- group: `project`
|
|
181
139
|
|
|
182
140
|
| Tool | Description |
|
|
183
141
|
|------|-------------|
|
|
@@ -192,7 +150,7 @@ For new projects, the agent is guided to call `project_register()` first.
|
|
|
192
150
|
| `project_shared_patterns` | Find patterns shared between two projects |
|
|
193
151
|
| `project_context` | Load full project context (memories, decisions, patterns, sessions) |
|
|
194
152
|
|
|
195
|
-
### Health
|
|
153
|
+
### Health (1 tool) -- group: `health`
|
|
196
154
|
|
|
197
155
|
| Tool | Description |
|
|
198
156
|
|------|-------------|
|
|
@@ -200,145 +158,200 @@ For new projects, the agent is guided to call `project_register()` first.
|
|
|
200
158
|
|
|
201
159
|
## Direct Commands
|
|
202
160
|
|
|
203
|
-
These slash commands bypass the LLM and execute immediately
|
|
161
|
+
These slash commands bypass the LLM and execute immediately.
|
|
162
|
+
|
|
163
|
+
### Inspection Commands
|
|
204
164
|
|
|
205
165
|
| Command | Description |
|
|
206
166
|
|---------|-------------|
|
|
207
|
-
| `/memory-
|
|
208
|
-
| `/memory-
|
|
209
|
-
| `/memory-
|
|
210
|
-
| `/memory-
|
|
211
|
-
| `/memory-
|
|
167
|
+
| `/memory-search <query>` | Semantic search across stored memories |
|
|
168
|
+
| `/memory-sessions` | List sessions (optional: `active`, `closed`, or project slug) |
|
|
169
|
+
| `/memory-decisions` | List architectural decisions (optional: project slug) |
|
|
170
|
+
| `/memory-patterns` | List or search patterns (optional: search query) |
|
|
171
|
+
| `/memory-entities` | List entities (optional: entity type filter) |
|
|
172
|
+
| `/memory-projects` | List registered projects |
|
|
173
|
+
| `/memory-agents` | List registered agents |
|
|
212
174
|
|
|
213
|
-
|
|
175
|
+
### Diagnostic Commands
|
|
214
176
|
|
|
215
|
-
|
|
177
|
+
| Command | Description |
|
|
178
|
+
|---------|-------------|
|
|
179
|
+
| `/memory-status` | Connection status, tool counts, and memory stats |
|
|
180
|
+
| `/memory-stats` | Daily statistics (total, growth, top categories) |
|
|
181
|
+
| `/memory-health` | API health check with response time |
|
|
182
|
+
| `/memory-logs` | Recent debug log entries (optional: limit, tool filter) |
|
|
183
|
+
| `/memory-metrics` | Per-tool call counts, success rates, and latency |
|
|
184
|
+
| `/memory-validate` | Production readiness checks |
|
|
185
|
+
| `/memory-config` | Display current plugin configuration |
|
|
216
186
|
|
|
217
|
-
|
|
218
|
-
{
|
|
219
|
-
"enabledTools": "memory,session,decision"
|
|
220
|
-
}
|
|
221
|
-
```
|
|
187
|
+
### Management Commands
|
|
222
188
|
|
|
223
|
-
|
|
189
|
+
| Command | Description |
|
|
190
|
+
|---------|-------------|
|
|
191
|
+
| `/memory-forget <id>` | Delete a specific memory by ID |
|
|
224
192
|
|
|
225
|
-
|
|
193
|
+
## Configuration Reference
|
|
226
194
|
|
|
227
|
-
|
|
195
|
+
```bash
|
|
196
|
+
openclaw config set plugins.entries.plugin-memoryrelay-ai.config '{
|
|
197
|
+
"apiKey": "mem_prod_...",
|
|
198
|
+
"agentId": "iris",
|
|
199
|
+
"defaultProject": "my-api",
|
|
200
|
+
"autoRecall": true,
|
|
201
|
+
"autoCapture": { "enabled": true, "tier": "smart", "confirmFirst": 5 }
|
|
202
|
+
}'
|
|
203
|
+
```
|
|
228
204
|
|
|
229
|
-
|
|
205
|
+
| Key | Type | Default | Description |
|
|
206
|
+
|-----|------|---------|-------------|
|
|
207
|
+
| `apiKey` | string | -- | MemoryRelay API key |
|
|
208
|
+
| `agentId` | string | -- | Unique agent identifier |
|
|
209
|
+
| `apiUrl` | string | `https://api.memoryrelay.net` | API endpoint |
|
|
210
|
+
| `defaultProject` | string | -- | Default project slug for sessions, decisions, and memories |
|
|
211
|
+
| `enabledTools` | string | `all` | Comma-separated tool groups to enable |
|
|
212
|
+
| `autoRecall` | boolean | `true` | Inject relevant memories into context each turn |
|
|
213
|
+
| `autoCapture` | boolean \| object | `true` | Auto-capture config (see tiers below) |
|
|
214
|
+
| `recallLimit` | number | `5` | Max memories injected per turn (1-20) |
|
|
215
|
+
| `recallThreshold` | number | `0.3` | Minimum similarity score for recall (0-1) |
|
|
216
|
+
| `excludeChannels` | string[] | `[]` | Channel IDs to skip auto-recall |
|
|
217
|
+
| `sessionTimeoutMinutes` | number | `120` | Idle time before session auto-close (10-1440) |
|
|
218
|
+
| `sessionCleanupIntervalMinutes` | number | `30` | Stale session check interval (5-360) |
|
|
219
|
+
| `debug` | boolean | `false` | Enable debug logging of API calls |
|
|
220
|
+
| `verbose` | boolean | `false` | Include request/response bodies in logs |
|
|
221
|
+
| `maxLogEntries` | number | `100` | Circular buffer size for in-memory logs (10-10000) |
|
|
230
222
|
|
|
231
|
-
|
|
223
|
+
### Environment Variables
|
|
232
224
|
|
|
233
|
-
|
|
234
|
-
|
|
225
|
+
| Variable | Maps to |
|
|
226
|
+
|----------|---------|
|
|
227
|
+
| `MEMORYRELAY_API_KEY` | `apiKey` |
|
|
228
|
+
| `MEMORYRELAY_AGENT_ID` | `agentId` |
|
|
229
|
+
| `MEMORYRELAY_API_URL` | `apiUrl` |
|
|
230
|
+
| `MEMORYRELAY_DEFAULT_PROJECT` | `defaultProject` |
|
|
235
231
|
|
|
236
|
-
|
|
237
|
-
[Injects workflow instructions + top 5 relevant memories into context]
|
|
238
|
-
Agent uses past decisions and patterns to inform its response
|
|
239
|
-
```
|
|
232
|
+
### Auto-Capture Tiers
|
|
240
233
|
|
|
241
|
-
|
|
234
|
+
| Tier | Behavior | Use When |
|
|
235
|
+
|------|----------|----------|
|
|
236
|
+
| `off` | Manual `memory_store` only | Full control, no surprises |
|
|
237
|
+
| `conservative` | Captures only low-risk technical facts | Sensitive environments |
|
|
238
|
+
| `smart` (default) | Balanced automation with privacy blocklist | Most teams |
|
|
239
|
+
| `aggressive` | Maximum capture, minimal filtering | Solo prototyping |
|
|
242
240
|
|
|
243
|
-
|
|
241
|
+
The `confirmFirst` setting (default: `5`) prompts for confirmation on the first N captures before running silently. The `blocklist` array accepts regex patterns for content that should never be captured.
|
|
244
242
|
|
|
245
243
|
```json
|
|
246
244
|
{
|
|
247
|
-
"
|
|
248
|
-
"
|
|
249
|
-
"
|
|
250
|
-
|
|
245
|
+
"autoCapture": {
|
|
246
|
+
"enabled": true,
|
|
247
|
+
"tier": "smart",
|
|
248
|
+
"confirmFirst": 5,
|
|
249
|
+
"blocklist": ["password", "secret", "Bearer\\s+\\S+"],
|
|
250
|
+
"categories": {
|
|
251
|
+
"credentials": true,
|
|
252
|
+
"preferences": true,
|
|
253
|
+
"technical": true,
|
|
254
|
+
"personal": false
|
|
255
|
+
}
|
|
256
|
+
}
|
|
251
257
|
}
|
|
252
258
|
```
|
|
253
259
|
|
|
254
|
-
##
|
|
260
|
+
## Architecture & Privacy
|
|
255
261
|
|
|
256
|
-
###
|
|
262
|
+
### Data Flow
|
|
257
263
|
|
|
258
|
-
```
|
|
259
|
-
|
|
260
|
-
"debug": true,
|
|
261
|
-
"verbose": false,
|
|
262
|
-
"maxLogEntries": 1000
|
|
263
|
-
}
|
|
264
|
+
```
|
|
265
|
+
Agent <-> Plugin <-> MemoryRelay API (HTTPS) <-> PostgreSQL + pgvector
|
|
264
266
|
```
|
|
265
267
|
|
|
266
|
-
|
|
268
|
+
All data in transit is encrypted via HTTPS. The plugin communicates with `api.memoryrelay.net` using bearer token authentication.
|
|
267
269
|
|
|
268
|
-
|
|
269
|
-
|--------|---------|---------|
|
|
270
|
-
| `memoryrelay.logs` | View debug logs | `openclaw gateway-call memoryrelay.logs '{"limit": 50}'` |
|
|
271
|
-
| `memoryrelay.health` | Run health check | `openclaw gateway-call memoryrelay.health` |
|
|
272
|
-
| `memoryrelay.test` | Test individual tools | `openclaw gateway-call memoryrelay.test '{"tool": "memory_store"}'` |
|
|
273
|
-
| `memoryrelay.metrics` | View performance stats | `openclaw gateway-call memoryrelay.metrics` |
|
|
274
|
-
| `memoryrelay.heartbeat` | Daily stats check | `openclaw gateway-call memoryrelay.heartbeat` |
|
|
275
|
-
| `memoryrelay.stats` | CLI stats command | `openclaw gateway-call memoryrelay.stats '{"format": "json"}'` |
|
|
276
|
-
| `memoryrelay.onboarding` | Show onboarding | `openclaw gateway-call memoryrelay.onboarding` |
|
|
277
|
-
| `memory.status` | Plugin status report | `openclaw gateway-call memory.status` |
|
|
270
|
+
### Privacy Controls
|
|
278
271
|
|
|
279
|
-
|
|
272
|
+
- **Blocklist regex patterns** in auto-capture config filter passwords, API keys, credit card numbers, SSNs, and other sensitive data before storage
|
|
273
|
+
- **Redaction hooks** on `before_message_write` and `tool_result_persist` apply blocklist patterns to messages and tool results before persistence
|
|
274
|
+
- **No credential storage** by default -- the `personal` category requires explicit opt-in
|
|
275
|
+
- **Channel exclusions** prevent auto-recall on sensitive channels
|
|
280
276
|
|
|
281
|
-
|
|
277
|
+
### Multi-Agent Support
|
|
282
278
|
|
|
283
|
-
|
|
279
|
+
- Each agent has its own memory namespace via `agentId`
|
|
280
|
+
- Projects, decisions, and patterns are shared across agents
|
|
281
|
+
- Subagent spawning and completion are tracked via lifecycle hooks (`subagent_spawned`, `subagent_ended`)
|
|
282
|
+
- Sender identity is auto-injected into memory metadata for traceability
|
|
284
283
|
|
|
285
|
-
|
|
286
|
-
- **API Key Auth** — Bearer token authentication
|
|
287
|
-
- **Agent Isolation** — Memories scoped per agent ID
|
|
288
|
-
- **Channel Filtering** — Exclude sensitive channels from auto-recall
|
|
289
|
-
- **Privacy Blocklist** — Auto-capture filters sensitive data (passwords, SSNs, credit cards, API keys)
|
|
290
|
-
- **Privacy Redaction Hooks** — Sensitive data is also redacted from messages (`before_message_write`) and tool results (`tool_result_persist`) before persistence
|
|
291
|
-
- **Sender Identity** — `memory_store`, `memory_batch_store`, and `decision_record` auto-inject `sender_id` from tool context into metadata for multi-agent traceability
|
|
292
|
-
- **Never store secrets** — Do not store API keys, passwords, or tokens as memories
|
|
284
|
+
### Lifecycle Hooks
|
|
293
285
|
|
|
294
|
-
|
|
286
|
+
The plugin registers 14 lifecycle hooks:
|
|
295
287
|
|
|
296
|
-
|
|
288
|
+
| Hook | Purpose |
|
|
289
|
+
|------|---------|
|
|
290
|
+
| `before_agent_start` | Auto-recall and workflow injection |
|
|
291
|
+
| `agent_end` | Auto-capture from completed conversations |
|
|
292
|
+
| `session_start` | Auto-create MemoryRelay session from OpenClaw session |
|
|
293
|
+
| `session_end` | Auto-end MemoryRelay session |
|
|
294
|
+
| `before_tool_call` | Reserved for future tool blocking/audit |
|
|
295
|
+
| `after_tool_call` | Session activity tracking and metrics |
|
|
296
|
+
| `before_compaction` | Save key context before compaction |
|
|
297
|
+
| `before_reset` | Save key context before session reset |
|
|
298
|
+
| `message_received` | Activity timestamp updates |
|
|
299
|
+
| `message_sending` | Reserved for future extensibility |
|
|
300
|
+
| `before_message_write` | Privacy redaction |
|
|
301
|
+
| `subagent_spawned` | Track multi-agent collaboration |
|
|
302
|
+
| `subagent_ended` | Store subagent completion summaries |
|
|
303
|
+
| `tool_result_persist` | Privacy redaction on tool results |
|
|
297
304
|
|
|
298
|
-
|
|
299
|
-
# Check if installed
|
|
300
|
-
npm list -g @memoryrelay/plugin-memoryrelay-ai
|
|
305
|
+
### Skills
|
|
301
306
|
|
|
302
|
-
|
|
303
|
-
openclaw plugins install @memoryrelay/plugin-memoryrelay-ai --force
|
|
307
|
+
The plugin ships with 8 skills providing guided workflows on top of the raw tools:
|
|
304
308
|
|
|
305
|
-
|
|
306
|
-
|
|
309
|
+
- **Agent-facing**: `memory-workflow`, `decision-tracking`, `pattern-management`, `project-orchestration`, `entity-and-context`
|
|
310
|
+
- **Developer-facing**: `codebase-navigation`, `testing-memoryrelay`, `release-process`
|
|
307
311
|
|
|
308
|
-
|
|
309
|
-
openclaw gateway logs | grep memoryrelay
|
|
310
|
-
```
|
|
312
|
+
## Troubleshooting
|
|
311
313
|
|
|
312
|
-
### API
|
|
314
|
+
### Connection refused / API key issues
|
|
313
315
|
|
|
314
316
|
```bash
|
|
315
|
-
# Test API directly
|
|
316
|
-
curl -H "X-API-Key:
|
|
317
|
+
# Test the API directly
|
|
318
|
+
curl -H "X-API-Key: $MEMORYRELAY_API_KEY" https://api.memoryrelay.net/v1/health
|
|
319
|
+
|
|
320
|
+
# Check plugin status
|
|
321
|
+
/memory-health
|
|
317
322
|
|
|
318
|
-
#
|
|
319
|
-
|
|
323
|
+
# Run full validation
|
|
324
|
+
/memory-validate
|
|
320
325
|
```
|
|
321
326
|
|
|
322
|
-
|
|
327
|
+
If `/memory-health` shows `connected: false`, verify your API key is set correctly via environment variable or config. Keys start with `mem_prod_`.
|
|
323
328
|
|
|
324
|
-
|
|
325
|
-
2. Check memories exist: `openclaw gateway-call memory_list '{"limit": 10}'`
|
|
326
|
-
3. Lower `recallThreshold` (try 0.1) for more results
|
|
327
|
-
4. Check channel not in `excludeChannels`
|
|
329
|
+
### Auto-recall not working
|
|
328
330
|
|
|
329
|
-
|
|
331
|
+
1. Confirm `autoRecall` is `true` (it is by default)
|
|
332
|
+
2. Verify memories exist: run `/memory-search test` to check
|
|
333
|
+
3. Lower `recallThreshold` to `0.1` for broader matching
|
|
334
|
+
4. Check your channel is not in `excludeChannels`
|
|
335
|
+
5. Run `/memory-status` to see the full plugin state
|
|
336
|
+
|
|
337
|
+
### Debug logging
|
|
330
338
|
|
|
331
|
-
|
|
332
|
-
- `memory_context`: Returns 405 Method Not Allowed (use `memory_recall` instead)
|
|
339
|
+
Enable debug mode to see all API calls:
|
|
333
340
|
|
|
334
|
-
|
|
341
|
+
```json
|
|
342
|
+
{
|
|
343
|
+
"debug": true,
|
|
344
|
+
"verbose": true,
|
|
345
|
+
"maxLogEntries": 1000
|
|
346
|
+
}
|
|
347
|
+
```
|
|
335
348
|
|
|
336
|
-
|
|
349
|
+
Then inspect with `/memory-logs` or `/memory-metrics` to identify slow or failing calls.
|
|
337
350
|
|
|
338
|
-
|
|
339
|
-
- **Developer-facing**: `codebase-navigation`, `testing-memoryrelay`, `release-process`
|
|
351
|
+
### Known Limitations
|
|
340
352
|
|
|
341
|
-
|
|
353
|
+
- `memory_batch_store`: May return 500 errors on large batches (use individual `memory_store` as workaround)
|
|
354
|
+
- `memory_context`: Returns 405 Method Not Allowed on some API versions (use `memory_recall` instead)
|
|
342
355
|
|
|
343
356
|
## Development
|
|
344
357
|
|
|
@@ -347,17 +360,14 @@ git clone https://github.com/memoryrelay/openclaw-plugin.git
|
|
|
347
360
|
cd openclaw-plugin
|
|
348
361
|
npm install
|
|
349
362
|
npm test
|
|
350
|
-
npm run test:watch
|
|
351
|
-
npm run test:coverage
|
|
352
363
|
```
|
|
353
364
|
|
|
354
|
-
## License
|
|
355
|
-
|
|
356
|
-
MIT License - see [LICENSE](./LICENSE) file
|
|
357
|
-
|
|
358
365
|
## Links
|
|
359
366
|
|
|
360
367
|
- **MemoryRelay**: https://memoryrelay.ai
|
|
361
368
|
- **OpenClaw**: https://docs.openclaw.ai
|
|
362
369
|
- **Repository**: https://github.com/memoryrelay/openclaw-plugin
|
|
363
|
-
|
|
370
|
+
|
|
371
|
+
## License
|
|
372
|
+
|
|
373
|
+
MIT
|
package/index.ts
CHANGED
|
@@ -4586,7 +4586,65 @@ export default async function plugin(api: OpenClawPluginApi): Promise<void> {
|
|
|
4586
4586
|
});
|
|
4587
4587
|
|
|
4588
4588
|
// ========================================================================
|
|
4589
|
-
//
|
|
4589
|
+
// Command Argument Parser (v0.14.0)
|
|
4590
|
+
// ========================================================================
|
|
4591
|
+
|
|
4592
|
+
function parseCommandArgs(input: string | undefined): { positional: string[]; flags: Record<string, string | boolean> } {
|
|
4593
|
+
const positional: string[] = [];
|
|
4594
|
+
const flags: Record<string, string | boolean> = {};
|
|
4595
|
+
|
|
4596
|
+
if (!input || input.trim() === "") {
|
|
4597
|
+
return { positional, flags };
|
|
4598
|
+
}
|
|
4599
|
+
|
|
4600
|
+
const tokens: string[] = [];
|
|
4601
|
+
let current = "";
|
|
4602
|
+
let inQuote: string | null = null;
|
|
4603
|
+
|
|
4604
|
+
for (const ch of input) {
|
|
4605
|
+
if (inQuote) {
|
|
4606
|
+
if (ch === inQuote) {
|
|
4607
|
+
inQuote = null;
|
|
4608
|
+
} else {
|
|
4609
|
+
current += ch;
|
|
4610
|
+
}
|
|
4611
|
+
} else if (ch === '"' || ch === "'") {
|
|
4612
|
+
inQuote = ch;
|
|
4613
|
+
} else if (ch === " " || ch === "\t") {
|
|
4614
|
+
if (current) {
|
|
4615
|
+
tokens.push(current);
|
|
4616
|
+
current = "";
|
|
4617
|
+
}
|
|
4618
|
+
} else {
|
|
4619
|
+
current += ch;
|
|
4620
|
+
}
|
|
4621
|
+
}
|
|
4622
|
+
if (current) tokens.push(current);
|
|
4623
|
+
|
|
4624
|
+
let i = 0;
|
|
4625
|
+
while (i < tokens.length) {
|
|
4626
|
+
const token = tokens[i];
|
|
4627
|
+
if (token.startsWith("--")) {
|
|
4628
|
+
const key = token.slice(2);
|
|
4629
|
+
const next = tokens[i + 1];
|
|
4630
|
+
if (next && !next.startsWith("--")) {
|
|
4631
|
+
flags[key] = next;
|
|
4632
|
+
i += 2;
|
|
4633
|
+
} else {
|
|
4634
|
+
flags[key] = true;
|
|
4635
|
+
i += 1;
|
|
4636
|
+
}
|
|
4637
|
+
} else {
|
|
4638
|
+
positional.push(token);
|
|
4639
|
+
i += 1;
|
|
4640
|
+
}
|
|
4641
|
+
}
|
|
4642
|
+
|
|
4643
|
+
return { positional, flags };
|
|
4644
|
+
}
|
|
4645
|
+
|
|
4646
|
+
// ========================================================================
|
|
4647
|
+
// Direct Commands (15 total) — bypass LLM, execute immediately
|
|
4590
4648
|
// ========================================================================
|
|
4591
4649
|
|
|
4592
4650
|
// /memory-status — Show full plugin status report
|
|
@@ -4789,6 +4847,438 @@ export default async function plugin(api: OpenClawPluginApi): Promise<void> {
|
|
|
4789
4847
|
},
|
|
4790
4848
|
});
|
|
4791
4849
|
|
|
4850
|
+
// ========================================================================
|
|
4851
|
+
// Direct Commands (10 new — v0.14.0)
|
|
4852
|
+
// ========================================================================
|
|
4853
|
+
|
|
4854
|
+
// /memory-search — Semantic memory search
|
|
4855
|
+
api.registerCommand?.({
|
|
4856
|
+
name: "memory-search",
|
|
4857
|
+
description: "Semantic search across stored memories",
|
|
4858
|
+
requireAuth: true,
|
|
4859
|
+
acceptsArgs: true,
|
|
4860
|
+
handler: async (ctx) => {
|
|
4861
|
+
try {
|
|
4862
|
+
const { positional, flags } = parseCommandArgs(ctx.args);
|
|
4863
|
+
const query = positional[0];
|
|
4864
|
+
if (!query) {
|
|
4865
|
+
return { text: "Usage: /memory-search <query> [--limit 10] [--project slug] [--threshold 0.3]" };
|
|
4866
|
+
}
|
|
4867
|
+
const limit = flags["limit"] ? parseInt(String(flags["limit"]), 10) : 10;
|
|
4868
|
+
const threshold = flags["threshold"] ? parseFloat(String(flags["threshold"])) : 0.3;
|
|
4869
|
+
const project = flags["project"] ? String(flags["project"]) : undefined;
|
|
4870
|
+
|
|
4871
|
+
const results = await client.search(query, limit, threshold, { project });
|
|
4872
|
+
const items: unknown[] = Array.isArray(results) ? results : (results as { data?: unknown[] }).data ?? [];
|
|
4873
|
+
|
|
4874
|
+
if (items.length === 0) {
|
|
4875
|
+
return { text: `No memories found for: "${query}"` };
|
|
4876
|
+
}
|
|
4877
|
+
|
|
4878
|
+
const lines: string[] = [`Memory Search: "${query}"`, "━".repeat(60)];
|
|
4879
|
+
for (const item of items) {
|
|
4880
|
+
const m = item as Record<string, unknown>;
|
|
4881
|
+
const content = String(m["content"] ?? "").slice(0, 120);
|
|
4882
|
+
const score = typeof m["similarity"] === "number" ? `${Math.round(m["similarity"] as number * 100)}%` : "N/A";
|
|
4883
|
+
const category = String(m["category"] ?? "general");
|
|
4884
|
+
const date = m["created_at"] ? new Date(String(m["created_at"])).toLocaleDateString() : "unknown";
|
|
4885
|
+
const id = String(m["id"] ?? "");
|
|
4886
|
+
lines.push(`[${score}] ${content}`);
|
|
4887
|
+
lines.push(` Category: ${category} | Date: ${date} | ID: ${id}`);
|
|
4888
|
+
}
|
|
4889
|
+
|
|
4890
|
+
return { text: lines.join("\n") };
|
|
4891
|
+
} catch (err) {
|
|
4892
|
+
return { text: `Error: ${String(err)}`, isError: true };
|
|
4893
|
+
}
|
|
4894
|
+
},
|
|
4895
|
+
});
|
|
4896
|
+
|
|
4897
|
+
// /memory-validate — Production readiness check
|
|
4898
|
+
api.registerCommand?.({
|
|
4899
|
+
name: "memory-validate",
|
|
4900
|
+
description: "Run production readiness checks for the MemoryRelay plugin",
|
|
4901
|
+
requireAuth: true,
|
|
4902
|
+
handler: async (_ctx) => {
|
|
4903
|
+
try {
|
|
4904
|
+
const results: Array<{ label: string; status: "PASS" | "FAIL" | "WARN"; detail: string }> = [];
|
|
4905
|
+
|
|
4906
|
+
// 1. API connectivity
|
|
4907
|
+
try {
|
|
4908
|
+
await client.health();
|
|
4909
|
+
results.push({ label: "API connectivity", status: "PASS", detail: "Health endpoint reachable" });
|
|
4910
|
+
} catch (err) {
|
|
4911
|
+
results.push({ label: "API connectivity", status: "FAIL", detail: String(err) });
|
|
4912
|
+
}
|
|
4913
|
+
|
|
4914
|
+
// 2. API health status
|
|
4915
|
+
try {
|
|
4916
|
+
const h = await client.health();
|
|
4917
|
+
const statusStr = String(h.status).toLowerCase();
|
|
4918
|
+
if (VALID_HEALTH_STATUSES.includes(statusStr)) {
|
|
4919
|
+
results.push({ label: "API health", status: "PASS", detail: `Status: ${h.status}` });
|
|
4920
|
+
} else {
|
|
4921
|
+
results.push({ label: "API health", status: "WARN", detail: `Unexpected status: ${h.status}` });
|
|
4922
|
+
}
|
|
4923
|
+
} catch (err) {
|
|
4924
|
+
results.push({ label: "API health", status: "FAIL", detail: String(err) });
|
|
4925
|
+
}
|
|
4926
|
+
|
|
4927
|
+
// 3. Core tools
|
|
4928
|
+
const allTools = Object.values(TOOL_GROUPS).flat();
|
|
4929
|
+
const coreTools = ["memory_store", "memory_recall", "memory_list"];
|
|
4930
|
+
const missing = coreTools.filter((t) => !allTools.includes(t));
|
|
4931
|
+
if (missing.length === 0) {
|
|
4932
|
+
results.push({ label: "Core tools", status: "PASS", detail: "memory_store, memory_recall, memory_list present" });
|
|
4933
|
+
} else {
|
|
4934
|
+
results.push({ label: "Core tools", status: "FAIL", detail: `Missing: ${missing.join(", ")}` });
|
|
4935
|
+
}
|
|
4936
|
+
|
|
4937
|
+
// 4. Auto-recall enabled
|
|
4938
|
+
const autoRecall = cfg?.autoRecall ?? true;
|
|
4939
|
+
results.push({
|
|
4940
|
+
label: "Auto-recall enabled",
|
|
4941
|
+
status: autoRecall ? "PASS" : "WARN",
|
|
4942
|
+
detail: autoRecall ? "Enabled" : "Disabled in config",
|
|
4943
|
+
});
|
|
4944
|
+
|
|
4945
|
+
// 5. Auto-capture enabled
|
|
4946
|
+
results.push({
|
|
4947
|
+
label: "Auto-capture enabled",
|
|
4948
|
+
status: autoCaptureConfig.enabled ? "PASS" : "WARN",
|
|
4949
|
+
detail: autoCaptureConfig.enabled ? `Enabled (tier: ${autoCaptureConfig.tier})` : "Disabled in config",
|
|
4950
|
+
});
|
|
4951
|
+
|
|
4952
|
+
// 6. Memory storage
|
|
4953
|
+
try {
|
|
4954
|
+
await client.list(1);
|
|
4955
|
+
results.push({ label: "Memory storage", status: "PASS", detail: "Storage accessible" });
|
|
4956
|
+
} catch (err) {
|
|
4957
|
+
results.push({ label: "Memory storage", status: "FAIL", detail: String(err) });
|
|
4958
|
+
}
|
|
4959
|
+
|
|
4960
|
+
// 7. Agent ID configured
|
|
4961
|
+
const agentIdOk = agentId && agentId !== "" && agentId !== "default";
|
|
4962
|
+
results.push({
|
|
4963
|
+
label: "Agent ID configured",
|
|
4964
|
+
status: agentIdOk ? "PASS" : "WARN",
|
|
4965
|
+
detail: agentIdOk ? `ID: ${agentId}` : `Agent ID is "${agentId}" — consider setting a unique ID`,
|
|
4966
|
+
});
|
|
4967
|
+
|
|
4968
|
+
const passes = results.filter((r) => r.status === "PASS").length;
|
|
4969
|
+
const failures = results.filter((r) => r.status === "FAIL").length;
|
|
4970
|
+
let grade: string;
|
|
4971
|
+
if (passes === 7) grade = "A+";
|
|
4972
|
+
else if (passes === 6) grade = "A";
|
|
4973
|
+
else if (passes === 5) grade = "B+";
|
|
4974
|
+
else if (passes === 4) grade = "B";
|
|
4975
|
+
else grade = "F";
|
|
4976
|
+
|
|
4977
|
+
const lines: string[] = ["MemoryRelay Production Readiness", "━".repeat(50)];
|
|
4978
|
+
for (const r of results) {
|
|
4979
|
+
lines.push(`[${r.status.padEnd(4)}] ${r.label}: ${r.detail}`);
|
|
4980
|
+
}
|
|
4981
|
+
lines.push("─".repeat(50));
|
|
4982
|
+
lines.push(`Checks passed: ${passes}/7 | Grade: ${grade} | Production ready: ${failures === 0 ? "Yes" : "No"}`);
|
|
4983
|
+
|
|
4984
|
+
return { text: lines.join("\n") };
|
|
4985
|
+
} catch (err) {
|
|
4986
|
+
return { text: `Error: ${String(err)}`, isError: true };
|
|
4987
|
+
}
|
|
4988
|
+
},
|
|
4989
|
+
});
|
|
4990
|
+
|
|
4991
|
+
// /memory-config — Read-only config display
|
|
4992
|
+
api.registerCommand?.({
|
|
4993
|
+
name: "memory-config",
|
|
4994
|
+
description: "Display current MemoryRelay plugin configuration",
|
|
4995
|
+
requireAuth: true,
|
|
4996
|
+
handler: async (_ctx) => {
|
|
4997
|
+
try {
|
|
4998
|
+
const lines: string[] = ["MemoryRelay Configuration", "━".repeat(50)];
|
|
4999
|
+
lines.push(`API URL: ${apiUrl}`);
|
|
5000
|
+
lines.push(`Agent ID: ${agentId}`);
|
|
5001
|
+
lines.push(`Default Project: ${defaultProject || "(none)"}`);
|
|
5002
|
+
lines.push(`Enabled Tools: ${cfg?.enabledTools ?? "all"}`);
|
|
5003
|
+
lines.push(`Auto-Recall: ${cfg?.autoRecall ?? true}`);
|
|
5004
|
+
lines.push(`Auto-Capture: ${autoCaptureConfig.enabled} (tier: ${autoCaptureConfig.tier})`);
|
|
5005
|
+
lines.push(`Recall Limit: ${cfg?.recallLimit ?? 5}`);
|
|
5006
|
+
lines.push(`Recall Threshold: ${cfg?.recallThreshold ?? 0.3}`);
|
|
5007
|
+
lines.push(`Exclude Channels: ${(cfg?.excludeChannels ?? []).join(", ") || "(none)"}`);
|
|
5008
|
+
lines.push(`Session Timeout: ${cfg?.sessionTimeoutMinutes ?? 120} min`);
|
|
5009
|
+
lines.push(`Cleanup Interval: ${cfg?.sessionCleanupIntervalMinutes ?? 30} min`);
|
|
5010
|
+
lines.push(`Debug: ${cfg?.debug ?? false}`);
|
|
5011
|
+
lines.push(`Verbose: ${cfg?.verbose ?? false}`);
|
|
5012
|
+
lines.push(`Max Log Entries: ${cfg?.maxLogEntries ?? 100}`);
|
|
5013
|
+
return { text: lines.join("\n") };
|
|
5014
|
+
} catch (err) {
|
|
5015
|
+
return { text: `Error: ${String(err)}`, isError: true };
|
|
5016
|
+
}
|
|
5017
|
+
},
|
|
5018
|
+
});
|
|
5019
|
+
|
|
5020
|
+
// /memory-sessions — List sessions
|
|
5021
|
+
api.registerCommand?.({
|
|
5022
|
+
name: "memory-sessions",
|
|
5023
|
+
description: "List MemoryRelay sessions",
|
|
5024
|
+
requireAuth: true,
|
|
5025
|
+
acceptsArgs: true,
|
|
5026
|
+
handler: async (ctx) => {
|
|
5027
|
+
try {
|
|
5028
|
+
const { flags } = parseCommandArgs(ctx.args);
|
|
5029
|
+
const limit = flags["limit"] ? parseInt(String(flags["limit"]), 10) : 10;
|
|
5030
|
+
const project = flags["project"] ? String(flags["project"]) : undefined;
|
|
5031
|
+
let status: string | undefined = flags["status"] ? String(flags["status"]) : undefined;
|
|
5032
|
+
if (flags["active"]) status = "active";
|
|
5033
|
+
|
|
5034
|
+
const raw = await client.listSessions(limit, project, status);
|
|
5035
|
+
const sessions: unknown[] = Array.isArray(raw) ? raw : (raw as { data?: unknown[] }).data ?? [];
|
|
5036
|
+
|
|
5037
|
+
if (sessions.length === 0) {
|
|
5038
|
+
return { text: "No sessions found." };
|
|
5039
|
+
}
|
|
5040
|
+
|
|
5041
|
+
const lines: string[] = ["MemoryRelay Sessions", "━".repeat(60)];
|
|
5042
|
+
for (const session of sessions) {
|
|
5043
|
+
const s = session as Record<string, unknown>;
|
|
5044
|
+
const sid = String(s["id"] ?? "");
|
|
5045
|
+
const sessionStatus = String(s["status"] ?? "unknown").toUpperCase();
|
|
5046
|
+
const startedAt = s["started_at"] ? new Date(String(s["started_at"])).toLocaleString() : "unknown";
|
|
5047
|
+
let duration = "ongoing";
|
|
5048
|
+
if (s["started_at"] && s["ended_at"]) {
|
|
5049
|
+
const diffMs = new Date(String(s["ended_at"])).getTime() - new Date(String(s["started_at"])).getTime();
|
|
5050
|
+
const diffMin = Math.round(diffMs / 60000);
|
|
5051
|
+
duration = `${diffMin}m`;
|
|
5052
|
+
}
|
|
5053
|
+
const summary = String(s["summary"] ?? "").slice(0, 80);
|
|
5054
|
+
lines.push(`[${sessionStatus}] ${sid}`);
|
|
5055
|
+
lines.push(` Started: ${startedAt} | Duration: ${duration}`);
|
|
5056
|
+
if (summary) lines.push(` ${summary}`);
|
|
5057
|
+
}
|
|
5058
|
+
|
|
5059
|
+
return { text: lines.join("\n") };
|
|
5060
|
+
} catch (err) {
|
|
5061
|
+
return { text: `Error: ${String(err)}`, isError: true };
|
|
5062
|
+
}
|
|
5063
|
+
},
|
|
5064
|
+
});
|
|
5065
|
+
|
|
5066
|
+
// /memory-decisions — List decisions
|
|
5067
|
+
api.registerCommand?.({
|
|
5068
|
+
name: "memory-decisions",
|
|
5069
|
+
description: "List architectural decisions stored in MemoryRelay",
|
|
5070
|
+
requireAuth: true,
|
|
5071
|
+
acceptsArgs: true,
|
|
5072
|
+
handler: async (ctx) => {
|
|
5073
|
+
try {
|
|
5074
|
+
const { flags } = parseCommandArgs(ctx.args);
|
|
5075
|
+
const limit = flags["limit"] ? parseInt(String(flags["limit"]), 10) : 10;
|
|
5076
|
+
const project = flags["project"] ? String(flags["project"]) : undefined;
|
|
5077
|
+
const status = flags["status"] ? String(flags["status"]) : undefined;
|
|
5078
|
+
const tags = flags["tags"] ? String(flags["tags"]) : undefined;
|
|
5079
|
+
|
|
5080
|
+
const raw = await client.listDecisions(limit, project, status, tags);
|
|
5081
|
+
const decisions: unknown[] = Array.isArray(raw) ? raw : (raw as { data?: unknown[] }).data ?? [];
|
|
5082
|
+
|
|
5083
|
+
if (decisions.length === 0) {
|
|
5084
|
+
return { text: "No decisions found." };
|
|
5085
|
+
}
|
|
5086
|
+
|
|
5087
|
+
const lines: string[] = ["MemoryRelay Decisions", "━".repeat(60)];
|
|
5088
|
+
for (const decision of decisions) {
|
|
5089
|
+
const d = decision as Record<string, unknown>;
|
|
5090
|
+
const decisionStatus = String(d["status"] ?? "unknown").toUpperCase();
|
|
5091
|
+
const title = String(d["title"] ?? "(untitled)");
|
|
5092
|
+
const date = d["created_at"] ? new Date(String(d["created_at"])).toLocaleDateString() : "unknown";
|
|
5093
|
+
const rationale = String(d["rationale"] ?? "").slice(0, 100);
|
|
5094
|
+
lines.push(`[${decisionStatus}] ${title} (${date})`);
|
|
5095
|
+
if (rationale) lines.push(` ${rationale}`);
|
|
5096
|
+
}
|
|
5097
|
+
|
|
5098
|
+
return { text: lines.join("\n") };
|
|
5099
|
+
} catch (err) {
|
|
5100
|
+
return { text: `Error: ${String(err)}`, isError: true };
|
|
5101
|
+
}
|
|
5102
|
+
},
|
|
5103
|
+
});
|
|
5104
|
+
|
|
5105
|
+
// /memory-patterns — List/search patterns
|
|
5106
|
+
api.registerCommand?.({
|
|
5107
|
+
name: "memory-patterns",
|
|
5108
|
+
description: "List or search memory patterns",
|
|
5109
|
+
requireAuth: true,
|
|
5110
|
+
acceptsArgs: true,
|
|
5111
|
+
handler: async (ctx) => {
|
|
5112
|
+
try {
|
|
5113
|
+
const { positional, flags } = parseCommandArgs(ctx.args);
|
|
5114
|
+
const query = positional[0] ?? "";
|
|
5115
|
+
const limit = flags["limit"] ? parseInt(String(flags["limit"]), 10) : 10;
|
|
5116
|
+
const category = flags["category"] ? String(flags["category"]) : undefined;
|
|
5117
|
+
const project = flags["project"] ? String(flags["project"]) : undefined;
|
|
5118
|
+
|
|
5119
|
+
const raw = await client.searchPatterns(query, category, project, limit);
|
|
5120
|
+
const patterns: unknown[] = Array.isArray(raw) ? raw : (raw as { data?: unknown[] }).data ?? [];
|
|
5121
|
+
|
|
5122
|
+
if (patterns.length === 0) {
|
|
5123
|
+
return { text: query ? `No patterns found for: "${query}"` : "No patterns found." };
|
|
5124
|
+
}
|
|
5125
|
+
|
|
5126
|
+
const lines: string[] = ["MemoryRelay Patterns", "━".repeat(60)];
|
|
5127
|
+
for (const pattern of patterns) {
|
|
5128
|
+
const p = pattern as Record<string, unknown>;
|
|
5129
|
+
const name = String(p["name"] ?? "(unnamed)");
|
|
5130
|
+
const cat = String(p["category"] ?? "general");
|
|
5131
|
+
const description = String(p["description"] ?? "").slice(0, 100);
|
|
5132
|
+
lines.push(`${name} [${cat}]`);
|
|
5133
|
+
if (description) lines.push(` ${description}`);
|
|
5134
|
+
}
|
|
5135
|
+
|
|
5136
|
+
return { text: lines.join("\n") };
|
|
5137
|
+
} catch (err) {
|
|
5138
|
+
return { text: `Error: ${String(err)}`, isError: true };
|
|
5139
|
+
}
|
|
5140
|
+
},
|
|
5141
|
+
});
|
|
5142
|
+
|
|
5143
|
+
// /memory-entities — List entities
|
|
5144
|
+
api.registerCommand?.({
|
|
5145
|
+
name: "memory-entities",
|
|
5146
|
+
description: "List entities stored in MemoryRelay",
|
|
5147
|
+
requireAuth: true,
|
|
5148
|
+
acceptsArgs: true,
|
|
5149
|
+
handler: async (ctx) => {
|
|
5150
|
+
try {
|
|
5151
|
+
const { flags } = parseCommandArgs(ctx.args);
|
|
5152
|
+
const limit = flags["limit"] ? parseInt(String(flags["limit"]), 10) : 20;
|
|
5153
|
+
|
|
5154
|
+
const raw = await client.listEntities(limit);
|
|
5155
|
+
const entities: unknown[] = Array.isArray(raw) ? raw : (raw as { data?: unknown[] }).data ?? [];
|
|
5156
|
+
|
|
5157
|
+
if (entities.length === 0) {
|
|
5158
|
+
return { text: "No entities found." };
|
|
5159
|
+
}
|
|
5160
|
+
|
|
5161
|
+
const lines: string[] = ["MemoryRelay Entities", "━".repeat(60)];
|
|
5162
|
+
for (const entity of entities) {
|
|
5163
|
+
const e = entity as Record<string, unknown>;
|
|
5164
|
+
const name = String(e["name"] ?? "(unnamed)");
|
|
5165
|
+
const type = String(e["type"] ?? "unknown");
|
|
5166
|
+
const relationships = Array.isArray(e["relationships"]) ? e["relationships"].length : (typeof e["relationship_count"] === "number" ? e["relationship_count"] : 0);
|
|
5167
|
+
lines.push(`${name} [${type}] (${relationships} relationships)`);
|
|
5168
|
+
}
|
|
5169
|
+
|
|
5170
|
+
return { text: lines.join("\n") };
|
|
5171
|
+
} catch (err) {
|
|
5172
|
+
return { text: `Error: ${String(err)}`, isError: true };
|
|
5173
|
+
}
|
|
5174
|
+
},
|
|
5175
|
+
});
|
|
5176
|
+
|
|
5177
|
+
// /memory-projects — List projects
|
|
5178
|
+
api.registerCommand?.({
|
|
5179
|
+
name: "memory-projects",
|
|
5180
|
+
description: "List projects in MemoryRelay",
|
|
5181
|
+
requireAuth: true,
|
|
5182
|
+
acceptsArgs: true,
|
|
5183
|
+
handler: async (ctx) => {
|
|
5184
|
+
try {
|
|
5185
|
+
const { flags } = parseCommandArgs(ctx.args);
|
|
5186
|
+
const limit = flags["limit"] ? parseInt(String(flags["limit"]), 10) : 20;
|
|
5187
|
+
|
|
5188
|
+
const raw = await client.listProjects(limit);
|
|
5189
|
+
const projects: unknown[] = Array.isArray(raw) ? raw : (raw as { data?: unknown[] }).data ?? [];
|
|
5190
|
+
|
|
5191
|
+
if (projects.length === 0) {
|
|
5192
|
+
return { text: "No projects found." };
|
|
5193
|
+
}
|
|
5194
|
+
|
|
5195
|
+
const lines: string[] = ["MemoryRelay Projects", "━".repeat(60)];
|
|
5196
|
+
for (const project of projects) {
|
|
5197
|
+
const p = project as Record<string, unknown>;
|
|
5198
|
+
const slug = String(p["slug"] ?? "(no-slug)");
|
|
5199
|
+
const description = String(p["description"] ?? "").slice(0, 80);
|
|
5200
|
+
const memoryCount = typeof p["memory_count"] === "number" ? p["memory_count"] : 0;
|
|
5201
|
+
lines.push(`${slug} — ${description || "(no description)"} (${memoryCount} memories)`);
|
|
5202
|
+
}
|
|
5203
|
+
|
|
5204
|
+
return { text: lines.join("\n") };
|
|
5205
|
+
} catch (err) {
|
|
5206
|
+
return { text: `Error: ${String(err)}`, isError: true };
|
|
5207
|
+
}
|
|
5208
|
+
},
|
|
5209
|
+
});
|
|
5210
|
+
|
|
5211
|
+
// /memory-agents — List agents
|
|
5212
|
+
api.registerCommand?.({
|
|
5213
|
+
name: "memory-agents",
|
|
5214
|
+
description: "List agents registered in MemoryRelay",
|
|
5215
|
+
requireAuth: true,
|
|
5216
|
+
acceptsArgs: true,
|
|
5217
|
+
handler: async (ctx) => {
|
|
5218
|
+
try {
|
|
5219
|
+
const { flags } = parseCommandArgs(ctx.args);
|
|
5220
|
+
const limit = flags["limit"] ? parseInt(String(flags["limit"]), 10) : 20;
|
|
5221
|
+
|
|
5222
|
+
const raw = await client.listAgents(limit);
|
|
5223
|
+
const agents: unknown[] = Array.isArray(raw) ? raw : (raw as { data?: unknown[] }).data ?? [];
|
|
5224
|
+
|
|
5225
|
+
if (agents.length === 0) {
|
|
5226
|
+
return { text: "No agents found." };
|
|
5227
|
+
}
|
|
5228
|
+
|
|
5229
|
+
const lines: string[] = ["MemoryRelay Agents", "━".repeat(60)];
|
|
5230
|
+
for (const agent of agents) {
|
|
5231
|
+
const a = agent as Record<string, unknown>;
|
|
5232
|
+
const id = String(a["id"] ?? "(no-id)");
|
|
5233
|
+
const name = String(a["name"] ?? "");
|
|
5234
|
+
const description = String(a["description"] ?? "");
|
|
5235
|
+
lines.push(`${id}${name ? ` (${name})` : ""}${description ? `, ${description}` : ""}`);
|
|
5236
|
+
}
|
|
5237
|
+
|
|
5238
|
+
return { text: lines.join("\n") };
|
|
5239
|
+
} catch (err) {
|
|
5240
|
+
return { text: `Error: ${String(err)}`, isError: true };
|
|
5241
|
+
}
|
|
5242
|
+
},
|
|
5243
|
+
});
|
|
5244
|
+
|
|
5245
|
+
// /memory-forget — Delete a memory by ID
|
|
5246
|
+
api.registerCommand?.({
|
|
5247
|
+
name: "memory-forget",
|
|
5248
|
+
description: "Delete a specific memory by ID",
|
|
5249
|
+
requireAuth: true,
|
|
5250
|
+
acceptsArgs: true,
|
|
5251
|
+
handler: async (ctx) => {
|
|
5252
|
+
const { positional } = parseCommandArgs(ctx.args);
|
|
5253
|
+
const memoryId = positional[0];
|
|
5254
|
+
if (!memoryId) {
|
|
5255
|
+
return { text: "Usage: /memory-forget <memory-id>" };
|
|
5256
|
+
}
|
|
5257
|
+
try {
|
|
5258
|
+
let preview = "";
|
|
5259
|
+
try {
|
|
5260
|
+
const existing = await client.get(memoryId);
|
|
5261
|
+
const m = existing as Record<string, unknown>;
|
|
5262
|
+
preview = String(m["content"] ?? "").slice(0, 120);
|
|
5263
|
+
} catch (_) {
|
|
5264
|
+
// preview unavailable — proceed with delete
|
|
5265
|
+
}
|
|
5266
|
+
|
|
5267
|
+
await client.delete(memoryId);
|
|
5268
|
+
|
|
5269
|
+
const lines = [`Memory deleted: ${memoryId}`];
|
|
5270
|
+
if (preview) lines.push(`Content: ${preview}`);
|
|
5271
|
+
return { text: lines.join("\n") };
|
|
5272
|
+
} catch (err) {
|
|
5273
|
+
const msg = String(err);
|
|
5274
|
+
if (msg.toLowerCase().includes("not found") || msg.includes("404")) {
|
|
5275
|
+
return { text: `Memory not found: ${memoryId}`, isError: true };
|
|
5276
|
+
}
|
|
5277
|
+
return { text: `Error: ${msg}`, isError: true };
|
|
5278
|
+
}
|
|
5279
|
+
},
|
|
5280
|
+
});
|
|
5281
|
+
|
|
4792
5282
|
// ========================================================================
|
|
4793
5283
|
// Stale Session Cleanup Service (v0.13.0)
|
|
4794
5284
|
// ========================================================================
|
package/openclaw.plugin.json
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
"id": "plugin-memoryrelay-ai",
|
|
3
3
|
"kind": "memory",
|
|
4
4
|
"name": "MemoryRelay AI",
|
|
5
|
-
"description": "MemoryRelay v0.
|
|
6
|
-
"version": "0.
|
|
5
|
+
"description": "MemoryRelay v0.14.0 - Long-term memory with 15 commands, sessions, decisions, patterns & projects (api.memoryrelay.net)",
|
|
6
|
+
"version": "0.14.0",
|
|
7
7
|
"uiHints": {
|
|
8
8
|
"apiKey": {
|
|
9
9
|
"label": "MemoryRelay API Key",
|
|
@@ -57,6 +57,32 @@
|
|
|
57
57
|
"placeholder": "30",
|
|
58
58
|
"advanced": true,
|
|
59
59
|
"help": "How often to check for stale sessions (default: 30)"
|
|
60
|
+
},
|
|
61
|
+
"recallLimit": {
|
|
62
|
+
"label": "Recall Limit",
|
|
63
|
+
"placeholder": "5",
|
|
64
|
+
"help": "Maximum number of memories to inject per auto-recall (1-20)"
|
|
65
|
+
},
|
|
66
|
+
"recallThreshold": {
|
|
67
|
+
"label": "Recall Threshold",
|
|
68
|
+
"placeholder": "0.3",
|
|
69
|
+
"help": "Minimum similarity score for auto-recall (0-1, lower = more results)"
|
|
70
|
+
},
|
|
71
|
+
"debug": {
|
|
72
|
+
"label": "Debug Logging",
|
|
73
|
+
"help": "Enable debug logging of API calls",
|
|
74
|
+
"advanced": true
|
|
75
|
+
},
|
|
76
|
+
"verbose": {
|
|
77
|
+
"label": "Verbose Logging",
|
|
78
|
+
"help": "Include request/response bodies in debug logs (requires debug: true)",
|
|
79
|
+
"advanced": true
|
|
80
|
+
},
|
|
81
|
+
"maxLogEntries": {
|
|
82
|
+
"label": "Max Log Entries",
|
|
83
|
+
"placeholder": "100",
|
|
84
|
+
"help": "Circular buffer size for in-memory debug logs (10-10,000)",
|
|
85
|
+
"advanced": true
|
|
60
86
|
}
|
|
61
87
|
},
|
|
62
88
|
"configSchema": {
|
|
@@ -86,8 +112,34 @@
|
|
|
86
112
|
"description": "Comma-separated tool groups to enable: memory, entity, agent, session, decision, pattern, project, health, or 'all' (default: all)"
|
|
87
113
|
},
|
|
88
114
|
"autoCapture": {
|
|
89
|
-
"
|
|
90
|
-
|
|
115
|
+
"oneOf": [
|
|
116
|
+
{ "type": "boolean" },
|
|
117
|
+
{
|
|
118
|
+
"type": "object",
|
|
119
|
+
"properties": {
|
|
120
|
+
"enabled": { "type": "boolean", "default": true },
|
|
121
|
+
"tier": { "type": "string", "enum": ["off", "conservative", "smart", "aggressive"], "default": "smart" },
|
|
122
|
+
"confirmFirst": { "type": "number", "default": 5, "minimum": 0, "description": "Number of captures to confirm before auto-accepting" },
|
|
123
|
+
"categories": {
|
|
124
|
+
"type": "object",
|
|
125
|
+
"properties": {
|
|
126
|
+
"credentials": { "type": "boolean", "default": true },
|
|
127
|
+
"preferences": { "type": "boolean", "default": true },
|
|
128
|
+
"technical": { "type": "boolean", "default": true },
|
|
129
|
+
"personal": { "type": "boolean", "default": false, "description": "Privacy: personal info requires confirmation" }
|
|
130
|
+
}
|
|
131
|
+
},
|
|
132
|
+
"blocklist": {
|
|
133
|
+
"type": "array",
|
|
134
|
+
"items": { "type": "string" },
|
|
135
|
+
"default": [],
|
|
136
|
+
"description": "Regex patterns for content to never capture"
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
],
|
|
141
|
+
"default": true,
|
|
142
|
+
"description": "Auto-capture configuration. Use true/false for simple toggle, or object for fine-grained control."
|
|
91
143
|
},
|
|
92
144
|
"autoRecall": {
|
|
93
145
|
"type": "boolean",
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@memoryrelay/plugin-memoryrelay-ai",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "OpenClaw memory plugin for MemoryRelay API - sessions, decisions, patterns, projects & semantic search",
|
|
3
|
+
"version": "0.14.0",
|
|
4
|
+
"description": "OpenClaw memory plugin for MemoryRelay API - 15 commands, sessions, decisions, patterns, projects & semantic search",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.ts",
|
|
7
7
|
"scripts": {
|