hippo-memory 0.30.0 → 0.31.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 +12 -0
- package/dist/cli.js +63 -13
- package/dist/cli.js.map +1 -1
- package/dist/scope.d.ts +14 -0
- package/dist/scope.d.ts.map +1 -0
- package/dist/scope.js +35 -0
- package/dist/scope.js.map +1 -0
- package/dist/search.d.ts +6 -0
- package/dist/search.d.ts.map +1 -1
- package/dist/search.js +13 -0
- package/dist/search.js.map +1 -1
- package/dist/shared.d.ts +2 -0
- package/dist/shared.d.ts.map +1 -1
- package/dist/shared.js +3 -3
- package/dist/shared.js.map +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
|
@@ -60,6 +60,18 @@ hippo recall "data pipeline issues" --budget 2000
|
|
|
60
60
|
|
|
61
61
|
---
|
|
62
62
|
|
|
63
|
+
### What's new in v0.31.0
|
|
64
|
+
|
|
65
|
+
- **Scope-aware corrections.** Tag a memory with `hippo remember --scope plan-eng-review` and it only surfaces strongly when that scope is active again. Matching scope gets 1.5x boost, mismatching scope is suppressed 0.5x, unscoped memories stay neutral. Corrections said during one skill stop polluting unrelated contexts.
|
|
66
|
+
- **Auto-detect from env.** `HIPPO_SCOPE`, `GSTACK_SKILL`, `OPENCLAW_SKILL` populate the scope automatically. Explicit `--scope` on any command overrides.
|
|
67
|
+
- **`hippo explain --why`** now shows the `scope:` multiplier when it fires, so you can see why a memory got ranked up or down.
|
|
68
|
+
|
|
69
|
+
### What's new in v0.30.1
|
|
70
|
+
|
|
71
|
+
- **`hippo recall --layer <L>` is now a strict filter.** Previously the flag was accepted but silently dropped; other layers leaked into results. The RSI demo's `recall --layer trace` now does what it says.
|
|
72
|
+
- **`hippo status` prints a `Trace:` counter.** The new layer is visible in status output.
|
|
73
|
+
- **`hippo --version` / `-v`** works as expected. Previously errored.
|
|
74
|
+
|
|
63
75
|
### What's new in v0.30.0
|
|
64
76
|
|
|
65
77
|
- **Sequence binding for recursive-self-improvement agents.** New `Layer.Trace` memories store ordered `A → B → C → outcome` traces. Agents can `hippo trace record` explicitly, or just call `hippo session complete --outcome success` and let `hippo sleep` auto-promote completed sessions into queryable traces.
|
package/dist/cli.js
CHANGED
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
import * as path from 'path';
|
|
29
29
|
import * as fs from 'fs';
|
|
30
30
|
import * as os from 'os';
|
|
31
|
+
import { fileURLToPath } from 'node:url';
|
|
31
32
|
import { execFileSync, execSync, spawn } from 'child_process';
|
|
32
33
|
import { installJsonHooks, uninstallJsonHooks, resolveJsonHookPaths, detectInstalledTools, defaultSleepLogPath, ensureCodexWrapperInstalled, installCodexWrapper, uninstallCodexWrapper, resolveCodexSessionTranscript, resolveCodexWrapperPaths, } from './hooks.js';
|
|
33
34
|
import { createMemory, calculateStrength, calculateRewardFactor, deriveHalfLife, resolveConfidence, applyOutcome, computeSchemaFit, Layer, DECISION_HALF_LIFE_DAYS, } from './memory.js';
|
|
@@ -43,6 +44,7 @@ import { openHippoDb, closeHippoDb } from './db.js';
|
|
|
43
44
|
import { captureError, extractLessons, deduplicateLesson, runWatched, fetchGitLog, isGitRepo, } from './autolearn.js';
|
|
44
45
|
import { extractInvalidationTarget, invalidateMatching } from './invalidation.js';
|
|
45
46
|
import { extractPathTags } from './path-context.js';
|
|
47
|
+
import { detectScope, scopeMatch } from './scope.js';
|
|
46
48
|
import { getGlobalRoot, initGlobal, promoteToGlobal, shareMemory, listPeers, autoShare, transferScore, searchBothHybrid, syncGlobalToLocal, } from './shared.js';
|
|
47
49
|
import { DAILY_TASK_NAME, buildDailyRunnerCommand, listRegisteredWorkspaces, registerWorkspace, runDailyMaintenance, } from './scheduler.js';
|
|
48
50
|
import { importChatGPT, importClaude, importCursor, importGenericFile, importMarkdown, } from './importers.js';
|
|
@@ -412,6 +414,14 @@ function cmdRemember(hippoRoot, text, flags) {
|
|
|
412
414
|
if (!entry.tags.includes(pt))
|
|
413
415
|
entry.tags.push(pt);
|
|
414
416
|
}
|
|
417
|
+
// Scope tagging: explicit --scope or auto-detected
|
|
418
|
+
const explicitScope = flags['scope'] !== undefined ? String(flags['scope']).trim() : null;
|
|
419
|
+
const activeScope = explicitScope || detectScope();
|
|
420
|
+
if (activeScope) {
|
|
421
|
+
const scopeTag = `scope:${activeScope}`;
|
|
422
|
+
if (!entry.tags.includes(scopeTag))
|
|
423
|
+
entry.tags.push(scopeTag);
|
|
424
|
+
}
|
|
415
425
|
writeEntry(targetRoot, entry);
|
|
416
426
|
updateStats(targetRoot, { remembered: 1 });
|
|
417
427
|
const prefix = useGlobal ? '[global] ' : '';
|
|
@@ -458,6 +468,8 @@ async function cmdRecall(hippoRoot, query, flags) {
|
|
|
458
468
|
const minResults = flags['min-results'] !== undefined
|
|
459
469
|
? parseInt(String(flags['min-results']), 10)
|
|
460
470
|
: undefined;
|
|
471
|
+
const recallExplicitScope = flags['scope'] !== undefined ? String(flags['scope']).trim() : null;
|
|
472
|
+
const recallActiveScope = recallExplicitScope || detectScope();
|
|
461
473
|
let results;
|
|
462
474
|
if (usePhysics && !hasGlobal) {
|
|
463
475
|
results = await physicsSearch(query, localEntries, {
|
|
@@ -465,17 +477,18 @@ async function cmdRecall(hippoRoot, query, flags) {
|
|
|
465
477
|
hippoRoot,
|
|
466
478
|
physicsConfig: config.physics,
|
|
467
479
|
minResults,
|
|
480
|
+
scope: recallActiveScope,
|
|
468
481
|
});
|
|
469
482
|
}
|
|
470
483
|
else if (hasGlobal) {
|
|
471
484
|
// Use searchBothHybrid for merged results with embedding support
|
|
472
485
|
results = await searchBothHybrid(query, hippoRoot, globalRoot, {
|
|
473
|
-
budget, mmr: mmrEnabled, mmrLambda, localBump, minResults,
|
|
486
|
+
budget, mmr: mmrEnabled, mmrLambda, localBump, minResults, scope: recallActiveScope,
|
|
474
487
|
});
|
|
475
488
|
}
|
|
476
489
|
else {
|
|
477
490
|
results = await hybridSearch(query, localEntries, {
|
|
478
|
-
budget, hippoRoot, mmr: mmrEnabled, mmrLambda, minResults,
|
|
491
|
+
budget, hippoRoot, mmr: mmrEnabled, mmrLambda, minResults, scope: recallActiveScope,
|
|
479
492
|
});
|
|
480
493
|
}
|
|
481
494
|
// --outcome filter: drop trace entries whose trace_outcome !== target.
|
|
@@ -494,6 +507,16 @@ async function cmdRecall(hippoRoot, query, flags) {
|
|
|
494
507
|
return r.entry.trace_outcome === outcomeFilter;
|
|
495
508
|
});
|
|
496
509
|
}
|
|
510
|
+
// --layer filter: strict, drops entries whose layer does not match.
|
|
511
|
+
const layerFilter = flags['layer'] !== undefined ? String(flags['layer']).trim() : '';
|
|
512
|
+
if (layerFilter) {
|
|
513
|
+
const validLayers = Object.values(Layer);
|
|
514
|
+
if (!validLayers.includes(layerFilter)) {
|
|
515
|
+
console.error(`Invalid --layer: "${layerFilter}". Must be one of: ${validLayers.join(', ')}.`);
|
|
516
|
+
process.exit(1);
|
|
517
|
+
}
|
|
518
|
+
results = results.filter((r) => r.entry.layer === layerFilter);
|
|
519
|
+
}
|
|
497
520
|
if (limit < results.length) {
|
|
498
521
|
results = results.slice(0, limit);
|
|
499
522
|
}
|
|
@@ -591,6 +614,8 @@ async function cmdExplain(hippoRoot, query, flags) {
|
|
|
591
614
|
: flags['local-bump'] !== undefined
|
|
592
615
|
? parseFloat(String(flags['local-bump']))
|
|
593
616
|
: config.search.localBump;
|
|
617
|
+
const explainExplicitScope = flags['scope'] !== undefined ? String(flags['scope']).trim() : null;
|
|
618
|
+
const explainActiveScope = explainExplicitScope || detectScope();
|
|
594
619
|
let results;
|
|
595
620
|
let modeUsed;
|
|
596
621
|
if (usePhysics && !hasGlobal) {
|
|
@@ -599,18 +624,19 @@ async function cmdExplain(hippoRoot, query, flags) {
|
|
|
599
624
|
hippoRoot,
|
|
600
625
|
physicsConfig: config.physics,
|
|
601
626
|
explain: true,
|
|
627
|
+
scope: explainActiveScope,
|
|
602
628
|
});
|
|
603
629
|
modeUsed = 'physics';
|
|
604
630
|
}
|
|
605
631
|
else if (hasGlobal) {
|
|
606
632
|
results = await searchBothHybrid(query, hippoRoot, globalRoot, {
|
|
607
|
-
budget, explain: true, mmr: mmrEnabled, mmrLambda, localBump,
|
|
633
|
+
budget, explain: true, mmr: mmrEnabled, mmrLambda, localBump, scope: explainActiveScope,
|
|
608
634
|
});
|
|
609
635
|
modeUsed = 'searchBothHybrid';
|
|
610
636
|
}
|
|
611
637
|
else {
|
|
612
638
|
results = await hybridSearch(query, localEntries, {
|
|
613
|
-
budget, hippoRoot, explain: true, mmr: mmrEnabled, mmrLambda,
|
|
639
|
+
budget, hippoRoot, explain: true, mmr: mmrEnabled, mmrLambda, scope: explainActiveScope,
|
|
614
640
|
});
|
|
615
641
|
modeUsed = 'hybrid';
|
|
616
642
|
}
|
|
@@ -680,6 +706,8 @@ async function cmdExplain(hippoRoot, query, flags) {
|
|
|
680
706
|
console.log(` recency: x${fmt(b.recencyMultiplier, 3)} (age=${b.ageDays}d)`);
|
|
681
707
|
if (b.decisionBoost !== 1)
|
|
682
708
|
console.log(` decision: x${fmt(b.decisionBoost, 2)} (tagged 'decision')`);
|
|
709
|
+
if (b.scopeBoost !== 1)
|
|
710
|
+
console.log(` scope: x${fmt(b.scopeBoost, 2)} (scope tag ${b.scopeBoost > 1 ? 'match' : 'mismatch'})`);
|
|
683
711
|
if (b.pathBoost !== 1)
|
|
684
712
|
console.log(` path: x${fmt(b.pathBoost, 3)} (cwd path tag overlap)`);
|
|
685
713
|
if (b.sourceBump !== 1)
|
|
@@ -1586,6 +1614,7 @@ function cmdStatus(hippoRoot) {
|
|
|
1586
1614
|
console.log(` Buffer: ${byLayer[Layer.Buffer]}`);
|
|
1587
1615
|
console.log(` Episodic: ${byLayer[Layer.Episodic]}`);
|
|
1588
1616
|
console.log(` Semantic: ${byLayer[Layer.Semantic]}`);
|
|
1617
|
+
console.log(` Trace: ${byLayer[Layer.Trace]}`);
|
|
1589
1618
|
const conflictCount = listMemoryConflicts(hippoRoot).length;
|
|
1590
1619
|
console.log(`Pinned: ${pinned}`);
|
|
1591
1620
|
console.log(`At risk (<0.2): ${atRisk}`);
|
|
@@ -2178,6 +2207,8 @@ async function cmdContext(hippoRoot, args, flags) {
|
|
|
2178
2207
|
}
|
|
2179
2208
|
const budget = parseInt(String(flags['budget'] ?? '1500'), 10);
|
|
2180
2209
|
const limit = parseLimitFlag(flags['limit']);
|
|
2210
|
+
const ctxExplicitScope = flags['scope'] !== undefined ? String(flags['scope']).trim() : null;
|
|
2211
|
+
const ctxActiveScope = ctxExplicitScope || detectScope();
|
|
2181
2212
|
// If budget is 0, skip entirely (zero token cost)
|
|
2182
2213
|
if (budget <= 0)
|
|
2183
2214
|
return;
|
|
@@ -2227,12 +2258,16 @@ async function cmdContext(hippoRoot, args, flags) {
|
|
|
2227
2258
|
...pinnedLocal.map((e) => ({ entry: e, isGlobal: false })),
|
|
2228
2259
|
...pinnedGlobal.map((e) => ({ entry: e, isGlobal: true })),
|
|
2229
2260
|
]
|
|
2230
|
-
.map(({ entry, isGlobal }) =>
|
|
2231
|
-
entry,
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2261
|
+
.map(({ entry, isGlobal }) => {
|
|
2262
|
+
const scopeSig = scopeMatch(entry.tags, ctxActiveScope);
|
|
2263
|
+
const sBst = scopeSig === 1 ? 1.5 : scopeSig === -1 ? 0.5 : 1.0;
|
|
2264
|
+
return {
|
|
2265
|
+
entry,
|
|
2266
|
+
score: calculateStrength(entry, nowP) * (isGlobal ? 1 / 1.2 : 1) * sBst,
|
|
2267
|
+
tokens: estimateTokens(entry.content),
|
|
2268
|
+
isGlobal,
|
|
2269
|
+
};
|
|
2270
|
+
})
|
|
2236
2271
|
.sort((a, b) => b.score - a.score);
|
|
2237
2272
|
let usedP = 0;
|
|
2238
2273
|
for (const r of rankedPinned) {
|
|
@@ -2275,7 +2310,7 @@ async function cmdContext(hippoRoot, args, flags) {
|
|
|
2275
2310
|
else {
|
|
2276
2311
|
let results;
|
|
2277
2312
|
if (hasGlobal) {
|
|
2278
|
-
const merged = await searchBothHybrid(query, hippoRoot, globalRoot, { budget });
|
|
2313
|
+
const merged = await searchBothHybrid(query, hippoRoot, globalRoot, { budget, scope: ctxActiveScope });
|
|
2279
2314
|
const localIndex = loadIndex(hippoRoot);
|
|
2280
2315
|
results = merged.map((r) => ({
|
|
2281
2316
|
entry: r.entry,
|
|
@@ -2288,8 +2323,8 @@ async function cmdContext(hippoRoot, args, flags) {
|
|
|
2288
2323
|
const ctxConfig = loadConfig(hippoRoot);
|
|
2289
2324
|
const usePhysicsCtx = ctxConfig.physics?.enabled !== false;
|
|
2290
2325
|
const ctxResults = usePhysicsCtx
|
|
2291
|
-
? await physicsSearch(query, localEntries, { budget, hippoRoot, physicsConfig: ctxConfig.physics })
|
|
2292
|
-
: await hybridSearch(query, localEntries, { budget, hippoRoot });
|
|
2326
|
+
? await physicsSearch(query, localEntries, { budget, hippoRoot, physicsConfig: ctxConfig.physics, scope: ctxActiveScope })
|
|
2327
|
+
: await hybridSearch(query, localEntries, { budget, hippoRoot, scope: ctxActiveScope });
|
|
2293
2328
|
results = ctxResults.map((r) => ({
|
|
2294
2329
|
entry: r.entry,
|
|
2295
2330
|
score: r.score,
|
|
@@ -3468,6 +3503,21 @@ Examples:
|
|
|
3468
3503
|
const { command, args, flags } = parseArgs(process.argv);
|
|
3469
3504
|
const hippoRoot = getHippoRoot(process.cwd());
|
|
3470
3505
|
async function main() {
|
|
3506
|
+
if (command === '--version' || command === '-v' || flags['version']) {
|
|
3507
|
+
const __filename_local = fileURLToPath(import.meta.url);
|
|
3508
|
+
const __dirname_local = path.dirname(__filename_local);
|
|
3509
|
+
const pkgPath = path.join(__dirname_local, '..', 'package.json');
|
|
3510
|
+
let pkgJson;
|
|
3511
|
+
try {
|
|
3512
|
+
pkgJson = fs.readFileSync(pkgPath, 'utf-8');
|
|
3513
|
+
}
|
|
3514
|
+
catch {
|
|
3515
|
+
pkgJson = fs.readFileSync(path.join(__dirname_local, '..', '..', 'package.json'), 'utf-8');
|
|
3516
|
+
}
|
|
3517
|
+
const { version } = JSON.parse(pkgJson);
|
|
3518
|
+
console.log(version);
|
|
3519
|
+
process.exit(0);
|
|
3520
|
+
}
|
|
3471
3521
|
maybeAutoInstallCodexWrapper(command, args);
|
|
3472
3522
|
switch (command) {
|
|
3473
3523
|
case 'init':
|