hippo-memory 1.64.0 → 1.64.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/cli/maintenance.d.ts +2 -1
- package/dist/cli/maintenance.js +17 -11
- package/dist/embedding-provider.js +4 -3
- package/dist/local-embedding.d.ts +2 -0
- package/dist/local-embedding.js +20 -4
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/extensions/openclaw-plugin/openclaw.plugin.json +1 -1
- package/extensions/openclaw-plugin/package.json +1 -1
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -994,7 +994,7 @@ Three benchmarks testing three different things. Full details in [`benchmarks/`]
|
|
|
994
994
|
|
|
995
995
|
[LongMemEval](https://arxiv.org/abs/2410.10813) (ICLR 2025) is the industry-standard benchmark: 500 questions across 5 memory abilities, embedded in 115k+ token chat histories.
|
|
996
996
|
|
|
997
|
-
**`hippo recall` (measured 2026-09-28 on hippo-memory 1.52.5).** On a default install, `hippo recall` puts an answer session in its top 5 for 85.6% of the 500 `_s` questions (95% CI 82.4 to 88.6), and 87.
|
|
997
|
+
**`hippo recall` (measured 2026-09-28 on hippo-memory 1.52.5, re-run 2026-10-06 on 1.64.0).** On a default install, `hippo recall` puts an answer session in its top 5 for 85.6% of the 500 `_s` questions (95% CI 82.4 to 88.6), and 87.8% with the optional MiniLM embedder (84.8 to 90.6; 87.6% on 1.52.5). Most of the gap to the scripts below is the default 4,000-token budget. Each memory in this test is a whole session, about 2,600 tokens at the median, so recall returns a median of 2 sessions. With the budget lifted, the same rankings score 96.8% (95.2 to 98.2) and 97.4% (96.0 to 98.6). A lifted call returns every candidate, a median of 47 sessions and 123,491 tokens per question, so those two figures measure the ranking, not an amount of text an agent could take in. Result: [`docs/evals/2026-09-28-recall-cli-longmemeval-result.md`](docs/evals/2026-09-28-recall-cli-longmemeval-result.md); re-run: [`docs/evals/2026-10-06-recall-cli-longmemeval-rerun-1.64.0.md`](docs/evals/2026-10-06-recall-cli-longmemeval-rerun-1.64.0.md).
|
|
998
998
|
|
|
999
999
|
**The benchmark scripts (`_s` split; MiniLM re-measured 2026-09-23, voyage measured 2026-06-09).** Each question is scored against its own ~48-session haystack, the same way gbrain and other published systems report. The v1.23.0 pluggable embedding provider lets you choose the embedder:
|
|
1000
1000
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
+
import { type EmbeddingProvider } from '../embedding-provider.js';
|
|
1
2
|
export declare function cmdRefine(hippoRoot: string, flags: Record<string, string | boolean | string[]>): Promise<void>;
|
|
2
3
|
export declare function cmdDedup(hippoRoot: string, flags: Record<string, string | boolean | string[]>): void;
|
|
3
|
-
export declare function cmdEmbed(hippoRoot: string, flags: Record<string, string | boolean | string[]
|
|
4
|
+
export declare function cmdEmbed(hippoRoot: string, flags: Record<string, string | boolean | string[]>, given?: EmbeddingProvider): Promise<void>;
|
|
4
5
|
//# sourceMappingURL=maintenance.d.ts.map
|
package/dist/cli/maintenance.js
CHANGED
|
@@ -3,7 +3,6 @@ import { envAnthropicApiKey } from '../env.js';
|
|
|
3
3
|
import { loadAllEntries } from '../store/entry-reads.js';
|
|
4
4
|
import { deduplicateStore } from '../dedupe.js';
|
|
5
5
|
import { embedAll, loadEmbeddingIndex } from '../embeddings.js';
|
|
6
|
-
import { resolveEmbeddingModel } from '../local-embedding.js';
|
|
7
6
|
import { resolveEmbeddingProvider } from '../embedding-provider.js';
|
|
8
7
|
import { resetAllPhysicsState } from '../physics-state.js';
|
|
9
8
|
import { loadConfig } from '../config.js';
|
|
@@ -93,7 +92,7 @@ export function cmdDedup(hippoRoot, flags) {
|
|
|
93
92
|
// ---------------------------------------------------------------------------
|
|
94
93
|
// Embed command
|
|
95
94
|
// ---------------------------------------------------------------------------
|
|
96
|
-
export async function cmdEmbed(hippoRoot, flags) {
|
|
95
|
+
export async function cmdEmbed(hippoRoot, flags, given) {
|
|
97
96
|
// --global mirrors resolveAuthRoot (cli.ts:6900): initGlobal() + the global
|
|
98
97
|
// root, skipping the local requireInit entirely, so this is the healing
|
|
99
98
|
// path for pre-1.27.0 global stores from a directory with no local .hippo.
|
|
@@ -109,12 +108,13 @@ export async function cmdEmbed(hippoRoot, flags) {
|
|
|
109
108
|
printEmbedStatus(root);
|
|
110
109
|
return;
|
|
111
110
|
}
|
|
112
|
-
|
|
111
|
+
const provider = readyEmbedProvider(root, given);
|
|
112
|
+
if (!provider)
|
|
113
113
|
return;
|
|
114
114
|
console.log('Embedding all memories (this may take a moment on first run to download model)...');
|
|
115
115
|
let count;
|
|
116
116
|
try {
|
|
117
|
-
count = await embedAll(root,
|
|
117
|
+
count = await embedAll(root, undefined, provider);
|
|
118
118
|
}
|
|
119
119
|
catch (err) {
|
|
120
120
|
printError(`Embedding failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -126,6 +126,11 @@ export async function cmdEmbed(hippoRoot, flags) {
|
|
|
126
126
|
const entriesAfter = loadAllEntries(root);
|
|
127
127
|
const embIndexAfter = loadEmbeddingIndex(root);
|
|
128
128
|
console.log(`Done. ${count} new embeddings created. ${Object.keys(embIndexAfter).length}/${entriesAfter.length} total.`);
|
|
129
|
+
const unembedded = entriesAfter.filter((e) => !embIndexAfter[e.id]).length;
|
|
130
|
+
if (unembedded > 0) {
|
|
131
|
+
printError(`${unembedded} memories are still not embedded (the warnings above name them). Re-run \`hippo embed\` to retry.`);
|
|
132
|
+
process.exitCode = 1;
|
|
133
|
+
}
|
|
129
134
|
}
|
|
130
135
|
function resetPhysics(root) {
|
|
131
136
|
const entries = loadAllEntries(root);
|
|
@@ -154,10 +159,10 @@ function printEmbedStatus(root) {
|
|
|
154
159
|
console.log(` ${missing.length} memories need embedding (run \`hippo embed\` to embed them)`);
|
|
155
160
|
}
|
|
156
161
|
}
|
|
157
|
-
/**
|
|
158
|
-
function
|
|
162
|
+
/** The provider to embed with, or null after saying why none is usable. */
|
|
163
|
+
function readyEmbedProvider(root, given) {
|
|
159
164
|
// Embedding (unlike status/reset) needs an available provider.
|
|
160
|
-
const embedProvider = (() => {
|
|
165
|
+
const embedProvider = given ?? (() => {
|
|
161
166
|
try {
|
|
162
167
|
return resolveEmbeddingProvider(root);
|
|
163
168
|
}
|
|
@@ -168,23 +173,24 @@ function embedProviderReady(root) {
|
|
|
168
173
|
})();
|
|
169
174
|
if (!embedProvider) {
|
|
170
175
|
process.exitCode = 1;
|
|
171
|
-
return
|
|
176
|
+
return null;
|
|
172
177
|
}
|
|
173
178
|
if (embedProvider.isAvailable())
|
|
174
|
-
return
|
|
179
|
+
return embedProvider;
|
|
175
180
|
if (loadConfig(root).embeddings.enabled === false) {
|
|
176
181
|
console.log('Embeddings are disabled in config (embeddings.enabled = false). Set it to true or "auto" to enable.');
|
|
177
|
-
return
|
|
182
|
+
return null;
|
|
178
183
|
}
|
|
179
184
|
if (embedProvider.kind === 'local') {
|
|
180
185
|
console.log('Embeddings not available. Install @huggingface/transformers to enable:');
|
|
181
186
|
console.log(' npm install @huggingface/transformers');
|
|
187
|
+
process.exitCode = 1;
|
|
182
188
|
}
|
|
183
189
|
else {
|
|
184
190
|
printError(`Embedding provider '${embedProvider.kind}' is configured but ${embedProvider.keyEnv} is not set.`);
|
|
185
191
|
printError(`Export ${embedProvider.keyEnv}, or set config.embeddings.provider back to 'local'.`);
|
|
186
192
|
process.exitCode = 1;
|
|
187
193
|
}
|
|
188
|
-
return
|
|
194
|
+
return null;
|
|
189
195
|
}
|
|
190
196
|
//# sourceMappingURL=maintenance.js.map
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
* egress-blocked in the build sandbox).
|
|
28
28
|
*/
|
|
29
29
|
import { envByName } from './env.js';
|
|
30
|
-
import { getEmbedding, isEmbeddingAvailable, resolveEmbeddingModel, DEFAULT_EMBEDDING_MODEL, } from './local-embedding.js';
|
|
30
|
+
import { getEmbedding, isEmbeddingAvailable, requireLocalPipeline, resolveEmbeddingModel, DEFAULT_EMBEDDING_MODEL, } from './local-embedding.js';
|
|
31
31
|
import { loadConfig } from './config.js';
|
|
32
32
|
import { redactSecretsStrict } from './secret-detect.js';
|
|
33
33
|
import { fetchWithRetry } from './http-retry.js';
|
|
@@ -57,8 +57,9 @@ class LocalEmbeddingProvider {
|
|
|
57
57
|
return this.enabled && isEmbeddingAvailable();
|
|
58
58
|
}
|
|
59
59
|
async embed(texts, role) {
|
|
60
|
-
//
|
|
61
|
-
|
|
60
|
+
// A model that cannot load fails the call, as an API outage does; items then run one at a time on the shared pipeline.
|
|
61
|
+
if (texts.length > 0)
|
|
62
|
+
await requireLocalPipeline(this.model);
|
|
62
63
|
const out = [];
|
|
63
64
|
for (const text of texts) {
|
|
64
65
|
out.push(await getEmbedding(text, this.model, role));
|
|
@@ -7,6 +7,8 @@ export type EmbeddingRole = 'query' | 'passage';
|
|
|
7
7
|
export declare function prefixFor(model: string, role?: EmbeddingRole): string;
|
|
8
8
|
/** Check (synchronously) if @xenova/transformers or @huggingface/transformers is installed. */
|
|
9
9
|
export declare function isEmbeddingAvailable(): boolean;
|
|
10
|
+
/** Throws, with the reason, when the model's pipeline cannot load: a provider-level failure, not a per-item skip. */
|
|
11
|
+
export declare function requireLocalPipeline(model: string): Promise<void>;
|
|
10
12
|
export declare function resolveEmbeddingModel(hippoRoot: string, explicitModel?: string): string;
|
|
11
13
|
/** Embeds `text` with the local model, or returns `[]` when Transformers.js is missing or fails; `role` adds e5-style prefixes. */
|
|
12
14
|
export declare function getEmbedding(text: string, model?: string, role?: EmbeddingRole): Promise<number[]>;
|
package/dist/local-embedding.js
CHANGED
|
@@ -10,6 +10,7 @@ let _embeddingAvailable = null;
|
|
|
10
10
|
// A pipeline is expensive to load, so one instance per model is kept for the process.
|
|
11
11
|
const _pipelineInstances = new Map();
|
|
12
12
|
const _pipelineLoading = new Map();
|
|
13
|
+
const _pipelineErrors = new Map();
|
|
13
14
|
export const DEFAULT_EMBEDDING_MODEL = 'Xenova/all-MiniLM-L6-v2';
|
|
14
15
|
/** BGE was trained with CLS pooling; everything else gets mean, the sentence-transformers default, since CLS degrades mean-trained models. */
|
|
15
16
|
export function poolingFor(model) {
|
|
@@ -79,8 +80,10 @@ async function loadPipeline(model) {
|
|
|
79
80
|
const loading = (async () => {
|
|
80
81
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
81
82
|
const pkg = resolveTransformersPackage();
|
|
82
|
-
if (!pkg)
|
|
83
|
+
if (!pkg) {
|
|
84
|
+
_pipelineErrors.set(model, 'no transformers package is installed');
|
|
83
85
|
return null;
|
|
86
|
+
}
|
|
84
87
|
let pipelineFn = null;
|
|
85
88
|
try {
|
|
86
89
|
// SAFETY: the resolved module's shape is untyped by design (optional peer
|
|
@@ -98,11 +101,16 @@ async function loadPipeline(model) {
|
|
|
98
101
|
pipelineFn = mod.pipeline ?? mod.default?.pipeline;
|
|
99
102
|
}
|
|
100
103
|
catch (err) {
|
|
101
|
-
|
|
104
|
+
// String(err) keeps a Node error's [ERR_...] code, which callers match on.
|
|
105
|
+
const reason = `transformers import failed (${pkg}): ${String(err)}`;
|
|
106
|
+
log.debug(reason);
|
|
107
|
+
_pipelineErrors.set(model, reason);
|
|
102
108
|
return null;
|
|
103
109
|
}
|
|
104
|
-
if (!pipelineFn)
|
|
110
|
+
if (!pipelineFn) {
|
|
111
|
+
_pipelineErrors.set(model, `${pkg} exports no pipeline function`);
|
|
105
112
|
return null;
|
|
113
|
+
}
|
|
106
114
|
// The offline bundle used in egress-blocked sandboxes ships only the FP32 model, so use whichever file is on disk.
|
|
107
115
|
const cacheRoot = envModelCache();
|
|
108
116
|
const quantized = !cacheRoot
|
|
@@ -113,7 +121,9 @@ async function loadPipeline(model) {
|
|
|
113
121
|
return instance;
|
|
114
122
|
}
|
|
115
123
|
catch (err) {
|
|
116
|
-
|
|
124
|
+
const reason = `embedding pipeline load failed (${model}): ${String(err)}`;
|
|
125
|
+
log.debug(reason);
|
|
126
|
+
_pipelineErrors.set(model, reason);
|
|
117
127
|
return null;
|
|
118
128
|
}
|
|
119
129
|
finally {
|
|
@@ -123,6 +133,12 @@ async function loadPipeline(model) {
|
|
|
123
133
|
_pipelineLoading.set(model, loading);
|
|
124
134
|
return loading;
|
|
125
135
|
}
|
|
136
|
+
/** Throws, with the reason, when the model's pipeline cannot load: a provider-level failure, not a per-item skip. */
|
|
137
|
+
export async function requireLocalPipeline(model) {
|
|
138
|
+
if (await loadPipeline(model))
|
|
139
|
+
return;
|
|
140
|
+
throw new Error(`local embedding model did not load: ${_pipelineErrors.get(model) ?? 'unknown reason'}`);
|
|
141
|
+
}
|
|
126
142
|
export function resolveEmbeddingModel(hippoRoot, explicitModel) {
|
|
127
143
|
const direct = explicitModel?.trim();
|
|
128
144
|
if (direct)
|
package/dist/version.d.ts
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
* an ESM `import` can resolve cleanly, and a hardcoded constant survives
|
|
17
17
|
* any packager that drops .json files.
|
|
18
18
|
*/
|
|
19
|
-
export declare const PACKAGE_VERSION = "1.64.
|
|
19
|
+
export declare const PACKAGE_VERSION = "1.64.1";
|
|
20
20
|
/** The floor a store takes on its first expiring key: the first release with schema v53, set by hand in that release; scripts/check-expiring-keys-floor.mjs gates it. */
|
|
21
21
|
export declare const EXPIRING_KEYS_MIN_BINARY = "1.64.0";
|
|
22
22
|
/** The floor a store takes on its first session bind or owner snapshot: first release with v54; set by hand in that release. */
|
package/dist/version.js
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
* an ESM `import` can resolve cleanly, and a hardcoded constant survives
|
|
17
17
|
* any packager that drops .json files.
|
|
18
18
|
*/
|
|
19
|
-
export const PACKAGE_VERSION = '1.64.
|
|
19
|
+
export const PACKAGE_VERSION = '1.64.1';
|
|
20
20
|
/** The floor a store takes on its first expiring key: the first release with schema v53, set by hand in that release; scripts/check-expiring-keys-floor.mjs gates it. */
|
|
21
21
|
export const EXPIRING_KEYS_MIN_BINARY = '1.64.0';
|
|
22
22
|
/** The floor a store takes on its first session bind or owner snapshot: first release with v54; set by hand in that release. */
|
package/openclaw.plugin.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"id": "hippo-memory",
|
|
3
3
|
"name": "Hippo Memory",
|
|
4
4
|
"description": "Make your agent's memory work like a brain. Hippo is long-term memory for coding agents.",
|
|
5
|
-
"version": "1.64.
|
|
5
|
+
"version": "1.64.1",
|
|
6
6
|
"configSchema": {
|
|
7
7
|
"type": "object",
|
|
8
8
|
"additionalProperties": false,
|
package/package.json
CHANGED