hippo-memory 0.6.0 → 0.6.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/dist/mcp/server.js
CHANGED
|
@@ -299,7 +299,7 @@ function handleRequest(req) {
|
|
|
299
299
|
result: {
|
|
300
300
|
protocolVersion: '2024-11-05',
|
|
301
301
|
capabilities: { tools: {} },
|
|
302
|
-
serverInfo: { name: 'hippo-memory', version: '0.6.
|
|
302
|
+
serverInfo: { name: 'hippo-memory', version: '0.6.1' },
|
|
303
303
|
},
|
|
304
304
|
};
|
|
305
305
|
case 'notifications/initialized':
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# Hippo Memory - OpenClaw Plugin
|
|
2
|
+
|
|
3
|
+
Biologically-inspired memory for OpenClaw agents. Memories decay by default, retrieval strengthens them, errors stick longer, and sleep consolidation compresses episodes into patterns.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
# Copy plugin to OpenClaw extensions
|
|
9
|
+
cp -r extensions/openclaw-plugin ~/.openclaw/extensions/hippo-memory
|
|
10
|
+
|
|
11
|
+
# Or link for development
|
|
12
|
+
openclaw plugins install -l ./extensions/openclaw-plugin
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Configure
|
|
16
|
+
|
|
17
|
+
Add to `openclaw.json`:
|
|
18
|
+
|
|
19
|
+
```json5
|
|
20
|
+
{
|
|
21
|
+
plugins: {
|
|
22
|
+
entries: {
|
|
23
|
+
"hippo-memory": {
|
|
24
|
+
enabled: true,
|
|
25
|
+
config: {
|
|
26
|
+
budget: 1500, // token budget for context injection
|
|
27
|
+
autoContext: true, // auto-inject memory at session start
|
|
28
|
+
autoLearn: true, // auto-capture errors
|
|
29
|
+
autoSleep: false, // auto-consolidate after heavy sessions
|
|
30
|
+
framing: "observe" // observe | suggest | assert
|
|
31
|
+
// root: "C:/path/to/workspace/.hippo" // optional override
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Restart the gateway after enabling.
|
|
40
|
+
|
|
41
|
+
## What It Does
|
|
42
|
+
|
|
43
|
+
### Auto-context injection
|
|
44
|
+
|
|
45
|
+
At every session start, hippo automatically injects relevant project memories into the system prompt. No manual `hippo context` needed. The agent sees memories from past sessions without any explicit tool calls.
|
|
46
|
+
|
|
47
|
+
By default the plugin runs Hippo from the current agent workspace, so Hippo uses that
|
|
48
|
+
workspace's local `.hippo/` store and automatically merges any global `~/.hippo/`
|
|
49
|
+
store during `recall` / `context`. You only need `config.root` if you want to
|
|
50
|
+
override workspace auto-detection.
|
|
51
|
+
|
|
52
|
+
### Agent tools
|
|
53
|
+
|
|
54
|
+
The plugin registers these tools for the agent to call:
|
|
55
|
+
|
|
56
|
+
| Tool | Description |
|
|
57
|
+
|------|-------------|
|
|
58
|
+
| `hippo_recall` | Search memories by topic (always available) |
|
|
59
|
+
| `hippo_remember` | Store a new memory (always available) |
|
|
60
|
+
| `hippo_outcome` | Report if recalled memories helped (always available) |
|
|
61
|
+
| `hippo_status` | Check memory health (optional, enable in tools.allow) |
|
|
62
|
+
| `hippo_context` | Smart context from git state (optional, enable in tools.allow) |
|
|
63
|
+
|
|
64
|
+
### How it differs from claude-mem
|
|
65
|
+
|
|
66
|
+
| | Hippo | claude-mem |
|
|
67
|
+
|---|---|---|
|
|
68
|
+
| Decay | Yes, 7-day half-life | No, saves everything |
|
|
69
|
+
| Retrieval strengthening | Yes | No |
|
|
70
|
+
| Outcome feedback | Yes | No |
|
|
71
|
+
| Cross-tool | Claude Code, Codex, Cursor, OpenClaw | Claude Code only |
|
|
72
|
+
| API calls | Zero | Uses Claude API for compression |
|
|
73
|
+
| Token cost | ~1500 tokens/session (configurable) | Variable |
|
|
74
|
+
| Memecoin | No | Yes ($CMEM on Solana) |
|
|
75
|
+
|
|
76
|
+
## Requirements
|
|
77
|
+
|
|
78
|
+
- `hippo-memory` CLI installed globally: `npm install -g hippo-memory`
|
|
79
|
+
- A `.hippo/` directory in your workspace: `hippo init`
|
|
80
|
+
- Optional shared global store: `hippo init --global`
|
|
@@ -0,0 +1,370 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hippo Memory - OpenClaw Plugin
|
|
3
|
+
*
|
|
4
|
+
* Auto-injects relevant memory context at session start,
|
|
5
|
+
* captures errors during sessions, and runs consolidation.
|
|
6
|
+
*
|
|
7
|
+
* Config lives under plugins.entries.hippo-memory.config
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { execSync } from 'child_process';
|
|
11
|
+
import { existsSync } from 'fs';
|
|
12
|
+
import { basename, dirname, join } from 'path';
|
|
13
|
+
|
|
14
|
+
interface HippoConfig {
|
|
15
|
+
budget?: number;
|
|
16
|
+
autoContext?: boolean;
|
|
17
|
+
autoLearn?: boolean;
|
|
18
|
+
autoSleep?: boolean;
|
|
19
|
+
framing?: 'observe' | 'suggest' | 'assert';
|
|
20
|
+
root?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
type HippoRuntimeContext = {
|
|
24
|
+
workspaceDir?: string;
|
|
25
|
+
agentId?: string;
|
|
26
|
+
sessionId?: string;
|
|
27
|
+
sessionKey?: string;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const AUTO_SLEEP_SESSION_THRESHOLD = 10;
|
|
31
|
+
const sessionMemoryCounts = new Map<string, number>();
|
|
32
|
+
|
|
33
|
+
function getConfig(api: any): HippoConfig {
|
|
34
|
+
try {
|
|
35
|
+
const entries = api.config?.plugins?.entries?.['hippo-memory'];
|
|
36
|
+
return entries?.config ?? {};
|
|
37
|
+
} catch {
|
|
38
|
+
return {};
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function findHippoRoot(workspace?: string, configRoot?: string): string | null {
|
|
43
|
+
if (configRoot && existsSync(configRoot)) return configRoot;
|
|
44
|
+
|
|
45
|
+
const candidates = [
|
|
46
|
+
workspace ? join(workspace, '.hippo') : null,
|
|
47
|
+
process.env.HIPPO_ROOT,
|
|
48
|
+
join(process.env.USERPROFILE || process.env.HOME || '', '.hippo'),
|
|
49
|
+
].filter(Boolean) as string[];
|
|
50
|
+
|
|
51
|
+
for (const candidate of candidates) {
|
|
52
|
+
if (existsSync(candidate)) return candidate;
|
|
53
|
+
}
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function getAgentWorkspace(api: any, agentId?: string): string | undefined {
|
|
58
|
+
try {
|
|
59
|
+
const agents = api.config?.agents;
|
|
60
|
+
const list = Array.isArray(agents?.list) ? agents.list : [];
|
|
61
|
+
|
|
62
|
+
if (agentId) {
|
|
63
|
+
const match = list.find((agent: any) => agent?.id === agentId);
|
|
64
|
+
if (typeof match?.workspace === 'string' && match.workspace) return match.workspace;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const defaultAgent = list.find((agent: any) => agent?.default);
|
|
68
|
+
if (typeof defaultAgent?.workspace === 'string' && defaultAgent.workspace) {
|
|
69
|
+
return defaultAgent.workspace;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const fallback = agents?.defaults?.workspace;
|
|
73
|
+
return typeof fallback === 'string' && fallback ? fallback : undefined;
|
|
74
|
+
} catch {
|
|
75
|
+
return undefined;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function resolveHippoCwd(workspace?: string, configRoot?: string): string {
|
|
80
|
+
const hippoRoot = findHippoRoot(workspace, configRoot);
|
|
81
|
+
if (!hippoRoot) return workspace || process.cwd();
|
|
82
|
+
return basename(hippoRoot).toLowerCase() === '.hippo' ? dirname(hippoRoot) : hippoRoot;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function resolveHippoCwdFromContext(api: any, ctx: HippoRuntimeContext, configRoot?: string): string {
|
|
86
|
+
const workspace = ctx.workspaceDir ?? getAgentWorkspace(api, ctx.agentId);
|
|
87
|
+
return resolveHippoCwd(workspace, configRoot);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function getSessionIdentity(ctx: Pick<HippoRuntimeContext, 'sessionId' | 'sessionKey' | 'agentId'>): string | undefined {
|
|
91
|
+
return ctx.sessionId ?? ctx.sessionKey ?? ctx.agentId;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function recordSessionMemory(ctx: Pick<HippoRuntimeContext, 'sessionId' | 'sessionKey' | 'agentId'>): void {
|
|
95
|
+
const key = getSessionIdentity(ctx);
|
|
96
|
+
if (!key) return;
|
|
97
|
+
sessionMemoryCounts.set(key, (sessionMemoryCounts.get(key) ?? 0) + 1);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function consumeSessionMemoryCount(
|
|
101
|
+
ctx: Pick<HippoRuntimeContext, 'sessionId' | 'sessionKey' | 'agentId'>,
|
|
102
|
+
): number {
|
|
103
|
+
const key = getSessionIdentity(ctx);
|
|
104
|
+
if (!key) return 0;
|
|
105
|
+
const count = sessionMemoryCounts.get(key) ?? 0;
|
|
106
|
+
sessionMemoryCounts.delete(key);
|
|
107
|
+
return count;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function sanitizeTag(tag?: string): string | undefined {
|
|
111
|
+
if (!tag) return undefined;
|
|
112
|
+
const normalized = tag
|
|
113
|
+
.toLowerCase()
|
|
114
|
+
.replace(/[^a-z0-9-]+/g, '-')
|
|
115
|
+
.replace(/^-+|-+$/g, '')
|
|
116
|
+
.slice(0, 30);
|
|
117
|
+
return normalized || undefined;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function formatToolErrorMemory(toolName: string, error: string): string {
|
|
121
|
+
const normalized = error.replace(/\s+/g, ' ').trim();
|
|
122
|
+
const truncated = normalized.slice(0, 500);
|
|
123
|
+
const suffix = normalized.length > truncated.length ? ' [truncated]' : '';
|
|
124
|
+
return `Tool '${toolName}' failed: ${truncated}${suffix}`;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function hippoRememberSucceeded(result: string): boolean {
|
|
128
|
+
return result.includes('Remembered [');
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function runHippo(args: string, cwd?: string): string {
|
|
132
|
+
try {
|
|
133
|
+
const result = execSync(`hippo ${args}`, {
|
|
134
|
+
cwd: cwd || process.cwd(),
|
|
135
|
+
timeout: 30000,
|
|
136
|
+
encoding: 'utf-8',
|
|
137
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
138
|
+
});
|
|
139
|
+
return result.trim();
|
|
140
|
+
} catch (err: any) {
|
|
141
|
+
return err.stdout?.trim() || err.message || 'hippo command failed';
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export default function register(api: any) {
|
|
146
|
+
const logger = api.logger ?? console;
|
|
147
|
+
|
|
148
|
+
// --- Tool: hippo_recall ---
|
|
149
|
+
api.registerTool((ctx: HippoRuntimeContext) => ({
|
|
150
|
+
name: 'hippo_recall',
|
|
151
|
+
description:
|
|
152
|
+
'Retrieve relevant memories from the project memory store. Returns memories ranked by relevance, strength, and recency within the token budget. Use at session start or when you need context about a topic.',
|
|
153
|
+
parameters: {
|
|
154
|
+
type: 'object',
|
|
155
|
+
properties: {
|
|
156
|
+
query: {
|
|
157
|
+
type: 'string',
|
|
158
|
+
description: 'What to search for in memory (natural language)',
|
|
159
|
+
},
|
|
160
|
+
budget: {
|
|
161
|
+
type: 'number',
|
|
162
|
+
description: 'Max tokens to return (default: 1500)',
|
|
163
|
+
},
|
|
164
|
+
},
|
|
165
|
+
required: ['query'],
|
|
166
|
+
},
|
|
167
|
+
async execute(_id: string, params: { query: string; budget?: number }) {
|
|
168
|
+
const cfg = getConfig(api);
|
|
169
|
+
const budget = params.budget ?? cfg.budget ?? 1500;
|
|
170
|
+
const framing = cfg.framing ?? 'observe';
|
|
171
|
+
const hippoCwd = resolveHippoCwdFromContext(api, ctx, cfg.root);
|
|
172
|
+
const result = runHippo(
|
|
173
|
+
`recall "${params.query.replace(/"/g, '\\"')}" --budget ${budget} --framing ${framing}`,
|
|
174
|
+
hippoCwd,
|
|
175
|
+
);
|
|
176
|
+
return { content: [{ type: 'text', text: result || 'No relevant memories found.' }] };
|
|
177
|
+
},
|
|
178
|
+
}));
|
|
179
|
+
|
|
180
|
+
// --- Tool: hippo_remember ---
|
|
181
|
+
api.registerTool((ctx: HippoRuntimeContext) => ({
|
|
182
|
+
name: 'hippo_remember',
|
|
183
|
+
description:
|
|
184
|
+
'Store a new memory. Use when you learn something non-obvious, hit an error, or discover a useful pattern. Memories decay over time unless retrieved. Errors get 2x half-life.',
|
|
185
|
+
parameters: {
|
|
186
|
+
type: 'object',
|
|
187
|
+
properties: {
|
|
188
|
+
text: {
|
|
189
|
+
type: 'string',
|
|
190
|
+
description: 'The memory to store (1-2 sentences, specific and concrete)',
|
|
191
|
+
},
|
|
192
|
+
error: {
|
|
193
|
+
type: 'boolean',
|
|
194
|
+
description: 'Mark as error memory (doubles half-life)',
|
|
195
|
+
},
|
|
196
|
+
pin: {
|
|
197
|
+
type: 'boolean',
|
|
198
|
+
description: 'Pin memory (never decays)',
|
|
199
|
+
},
|
|
200
|
+
tag: {
|
|
201
|
+
type: 'string',
|
|
202
|
+
description: 'Optional tag for categorization',
|
|
203
|
+
},
|
|
204
|
+
},
|
|
205
|
+
required: ['text'],
|
|
206
|
+
},
|
|
207
|
+
async execute(
|
|
208
|
+
_id: string,
|
|
209
|
+
params: { text: string; error?: boolean; pin?: boolean; tag?: string },
|
|
210
|
+
) {
|
|
211
|
+
const cfg = getConfig(api);
|
|
212
|
+
const hippoCwd = resolveHippoCwdFromContext(api, ctx, cfg.root);
|
|
213
|
+
let args = `remember "${params.text.replace(/"/g, '\\"')}"`;
|
|
214
|
+
if (params.error) args += ' --error';
|
|
215
|
+
if (params.pin) args += ' --pin';
|
|
216
|
+
if (params.tag) args += ` --tag ${params.tag}`;
|
|
217
|
+
const result = runHippo(args, hippoCwd);
|
|
218
|
+
if (hippoRememberSucceeded(result)) {
|
|
219
|
+
recordSessionMemory(ctx);
|
|
220
|
+
}
|
|
221
|
+
return { content: [{ type: 'text', text: result || 'Memory stored.' }] };
|
|
222
|
+
},
|
|
223
|
+
}));
|
|
224
|
+
|
|
225
|
+
// --- Tool: hippo_outcome ---
|
|
226
|
+
api.registerTool((ctx: HippoRuntimeContext) => ({
|
|
227
|
+
name: 'hippo_outcome',
|
|
228
|
+
description:
|
|
229
|
+
'Report whether recalled memories were useful. Strengthens good memories (+5 days half-life) and weakens bad ones (-3 days). Call after completing work.',
|
|
230
|
+
parameters: {
|
|
231
|
+
type: 'object',
|
|
232
|
+
properties: {
|
|
233
|
+
good: {
|
|
234
|
+
type: 'boolean',
|
|
235
|
+
description: 'true = memories helped, false = memories were irrelevant',
|
|
236
|
+
},
|
|
237
|
+
},
|
|
238
|
+
required: ['good'],
|
|
239
|
+
},
|
|
240
|
+
async execute(_id: string, params: { good: boolean }) {
|
|
241
|
+
const cfg = getConfig(api);
|
|
242
|
+
const hippoCwd = resolveHippoCwdFromContext(api, ctx, cfg.root);
|
|
243
|
+
const flag = params.good ? '--good' : '--bad';
|
|
244
|
+
const result = runHippo(`outcome ${flag}`, hippoCwd);
|
|
245
|
+
return { content: [{ type: 'text', text: result || 'Outcome recorded.' }] };
|
|
246
|
+
},
|
|
247
|
+
}));
|
|
248
|
+
|
|
249
|
+
// --- Tool: hippo_status ---
|
|
250
|
+
api.registerTool(
|
|
251
|
+
(ctx: HippoRuntimeContext) => ({
|
|
252
|
+
name: 'hippo_status',
|
|
253
|
+
description:
|
|
254
|
+
'Check memory health: counts, strengths, at-risk memories, last consolidation time.',
|
|
255
|
+
parameters: {
|
|
256
|
+
type: 'object',
|
|
257
|
+
properties: {},
|
|
258
|
+
},
|
|
259
|
+
async execute() {
|
|
260
|
+
const cfg = getConfig(api);
|
|
261
|
+
const hippoCwd = resolveHippoCwdFromContext(api, ctx, cfg.root);
|
|
262
|
+
const result = runHippo('status', hippoCwd);
|
|
263
|
+
return { content: [{ type: 'text', text: result || 'No hippo store found.' }] };
|
|
264
|
+
},
|
|
265
|
+
}),
|
|
266
|
+
{ optional: true },
|
|
267
|
+
);
|
|
268
|
+
|
|
269
|
+
// --- Tool: hippo_context ---
|
|
270
|
+
api.registerTool(
|
|
271
|
+
(ctx: HippoRuntimeContext) => ({
|
|
272
|
+
name: 'hippo_context',
|
|
273
|
+
description:
|
|
274
|
+
'Smart context injection: auto-detects current task from git state and returns relevant memories. Use at the start of any session.',
|
|
275
|
+
parameters: {
|
|
276
|
+
type: 'object',
|
|
277
|
+
properties: {
|
|
278
|
+
budget: {
|
|
279
|
+
type: 'number',
|
|
280
|
+
description: 'Max tokens (default: 1500)',
|
|
281
|
+
},
|
|
282
|
+
},
|
|
283
|
+
},
|
|
284
|
+
async execute(_id: string, params: { budget?: number }) {
|
|
285
|
+
const cfg = getConfig(api);
|
|
286
|
+
const budget = params.budget ?? cfg.budget ?? 1500;
|
|
287
|
+
const framing = cfg.framing ?? 'observe';
|
|
288
|
+
const hippoCwd = resolveHippoCwdFromContext(api, ctx, cfg.root);
|
|
289
|
+
const result = runHippo(
|
|
290
|
+
`context --auto --budget ${budget} --framing ${framing}`,
|
|
291
|
+
hippoCwd,
|
|
292
|
+
);
|
|
293
|
+
return { content: [{ type: 'text', text: result || 'No context available.' }] };
|
|
294
|
+
},
|
|
295
|
+
}),
|
|
296
|
+
{ optional: true },
|
|
297
|
+
);
|
|
298
|
+
|
|
299
|
+
// --- Hook: auto-inject context at session start ---
|
|
300
|
+
api.on(
|
|
301
|
+
'before_prompt_build',
|
|
302
|
+
(_event: any, ctx: HippoRuntimeContext) => {
|
|
303
|
+
const cfg = getConfig(api);
|
|
304
|
+
if (cfg.autoContext === false) return {};
|
|
305
|
+
|
|
306
|
+
const budget = cfg.budget ?? 1500;
|
|
307
|
+
const framing = cfg.framing ?? 'observe';
|
|
308
|
+
const hippoCwd = resolveHippoCwdFromContext(api, ctx, cfg.root);
|
|
309
|
+
|
|
310
|
+
try {
|
|
311
|
+
const context = runHippo(
|
|
312
|
+
`context --auto --budget ${budget} --framing ${framing}`,
|
|
313
|
+
hippoCwd,
|
|
314
|
+
);
|
|
315
|
+
if (context && context.length > 10 && !context.includes('No hippo store')) {
|
|
316
|
+
return {
|
|
317
|
+
appendSystemContext: `\n\n## Project Memory (Hippo)\n${context}`,
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
} catch (err) {
|
|
321
|
+
logger.debug?.('[hippo] context injection skipped:', err);
|
|
322
|
+
}
|
|
323
|
+
return {};
|
|
324
|
+
},
|
|
325
|
+
{ priority: 5 },
|
|
326
|
+
);
|
|
327
|
+
|
|
328
|
+
api.on(
|
|
329
|
+
'after_tool_call',
|
|
330
|
+
(event: { toolName: string; error?: string }, ctx: HippoRuntimeContext) => {
|
|
331
|
+
const cfg = getConfig(api);
|
|
332
|
+
if (cfg.autoLearn === false) return;
|
|
333
|
+
if (!event.error?.trim()) return;
|
|
334
|
+
if (event.toolName.startsWith('hippo_')) return;
|
|
335
|
+
|
|
336
|
+
const hippoCwd = resolveHippoCwdFromContext(api, ctx, cfg.root);
|
|
337
|
+
const toolTag = sanitizeTag(event.toolName);
|
|
338
|
+
let args =
|
|
339
|
+
`remember "${formatToolErrorMemory(event.toolName, event.error).replace(/"/g, '\\"')}"` +
|
|
340
|
+
' --error --observed --tag openclaw';
|
|
341
|
+
if (toolTag) args += ` --tag ${toolTag}`;
|
|
342
|
+
|
|
343
|
+
const result = runHippo(args, hippoCwd);
|
|
344
|
+
if (hippoRememberSucceeded(result)) {
|
|
345
|
+
recordSessionMemory(ctx);
|
|
346
|
+
} else {
|
|
347
|
+
logger.debug?.(`[hippo] autoLearn skipped storing tool error: ${result}`);
|
|
348
|
+
}
|
|
349
|
+
},
|
|
350
|
+
);
|
|
351
|
+
|
|
352
|
+
api.on(
|
|
353
|
+
'session_end',
|
|
354
|
+
(_event: { sessionId: string; messageCount: number }, ctx: HippoRuntimeContext) => {
|
|
355
|
+
const cfg = getConfig(api);
|
|
356
|
+
const newMemories = consumeSessionMemoryCount(ctx);
|
|
357
|
+
if (!cfg.autoSleep || newMemories < AUTO_SLEEP_SESSION_THRESHOLD) return;
|
|
358
|
+
|
|
359
|
+
const hippoCwd = resolveHippoCwdFromContext(api, ctx, cfg.root);
|
|
360
|
+
const result = runHippo('sleep', hippoCwd);
|
|
361
|
+
logger.info?.(
|
|
362
|
+
`[hippo] autoSleep ran for session ${ctx.sessionId ?? ctx.sessionKey ?? 'unknown'} ` +
|
|
363
|
+
`after ${newMemories} new memories`,
|
|
364
|
+
);
|
|
365
|
+
logger.debug?.(`[hippo] autoSleep result: ${result}`);
|
|
366
|
+
},
|
|
367
|
+
);
|
|
368
|
+
|
|
369
|
+
logger.info?.('[hippo] Memory plugin registered (tools: hippo_recall, hippo_remember, hippo_outcome, hippo_status, hippo_context)');
|
|
370
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "hippo-memory",
|
|
3
|
+
"name": "Hippo Memory",
|
|
4
|
+
"description": "Biologically-inspired memory for AI agents. Decay by default, retrieval strengthening, sleep consolidation.",
|
|
5
|
+
"version": "0.4.1",
|
|
6
|
+
|
|
7
|
+
"configSchema": {
|
|
8
|
+
"type": "object",
|
|
9
|
+
"additionalProperties": false,
|
|
10
|
+
"properties": {
|
|
11
|
+
"budget": {
|
|
12
|
+
"type": "number",
|
|
13
|
+
"description": "Default token budget for context injection (default: 1500)"
|
|
14
|
+
},
|
|
15
|
+
"autoContext": {
|
|
16
|
+
"type": "boolean",
|
|
17
|
+
"description": "Auto-inject hippo context at session start (default: true)"
|
|
18
|
+
},
|
|
19
|
+
"autoLearn": {
|
|
20
|
+
"type": "boolean",
|
|
21
|
+
"description": "Auto-capture errors during agent sessions (default: true)"
|
|
22
|
+
},
|
|
23
|
+
"autoSleep": {
|
|
24
|
+
"type": "boolean",
|
|
25
|
+
"description": "Run consolidation after sessions with 10+ new memories (default: false)"
|
|
26
|
+
},
|
|
27
|
+
"framing": {
|
|
28
|
+
"type": "string",
|
|
29
|
+
"enum": ["observe", "suggest", "assert"],
|
|
30
|
+
"description": "How memories are framed in context (default: observe)"
|
|
31
|
+
},
|
|
32
|
+
"root": {
|
|
33
|
+
"type": "string",
|
|
34
|
+
"description": "Custom .hippo directory path (default: auto-detect from workspace)"
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
"uiHints": {
|
|
39
|
+
"budget": { "label": "Token Budget", "placeholder": "1500" },
|
|
40
|
+
"autoContext": { "label": "Auto-inject Context" },
|
|
41
|
+
"autoLearn": { "label": "Auto-capture Errors" },
|
|
42
|
+
"autoSleep": { "label": "Auto-consolidate" },
|
|
43
|
+
"framing": { "label": "Memory Framing" },
|
|
44
|
+
"root": { "label": "Hippo Root Path", "placeholder": ".hippo/" }
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "hippo-memory",
|
|
3
|
+
"version": "0.4.1",
|
|
4
|
+
"description": "Hippo Memory plugin for OpenClaw - biologically-inspired agent memory",
|
|
5
|
+
"main": "index.ts",
|
|
6
|
+
"openclaw": {
|
|
7
|
+
"extensions": ["./index.ts"]
|
|
8
|
+
},
|
|
9
|
+
"peerDependencies": {
|
|
10
|
+
"hippo-memory": ">=0.4.0"
|
|
11
|
+
},
|
|
12
|
+
"license": "MIT"
|
|
13
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hippo-memory",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.1",
|
|
4
4
|
"description": "Biologically-inspired memory system for AI agents. Decay by default, strength through use.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -12,7 +12,8 @@
|
|
|
12
12
|
},
|
|
13
13
|
"files": [
|
|
14
14
|
"dist",
|
|
15
|
-
"bin"
|
|
15
|
+
"bin",
|
|
16
|
+
"extensions/openclaw-plugin"
|
|
16
17
|
],
|
|
17
18
|
"scripts": {
|
|
18
19
|
"build": "tsc",
|