hippo-memory 0.10.0 → 0.11.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.
Files changed (53) hide show
  1. package/README.md +97 -15
  2. package/dist/cli.js +93 -12
  3. package/dist/cli.js.map +1 -1
  4. package/dist/config.d.ts +2 -0
  5. package/dist/config.d.ts.map +1 -1
  6. package/dist/config.js +3 -0
  7. package/dist/config.js.map +1 -1
  8. package/dist/consolidate.d.ts +1 -0
  9. package/dist/consolidate.d.ts.map +1 -1
  10. package/dist/consolidate.js +61 -2
  11. package/dist/consolidate.js.map +1 -1
  12. package/dist/db.d.ts.map +1 -1
  13. package/dist/db.js +19 -1
  14. package/dist/db.js.map +1 -1
  15. package/dist/embeddings.d.ts +1 -0
  16. package/dist/embeddings.d.ts.map +1 -1
  17. package/dist/embeddings.js +31 -1
  18. package/dist/embeddings.js.map +1 -1
  19. package/dist/index.d.ts +1 -1
  20. package/dist/index.d.ts.map +1 -1
  21. package/dist/index.js +1 -1
  22. package/dist/index.js.map +1 -1
  23. package/dist/mcp/server.js +9 -3
  24. package/dist/mcp/server.js.map +1 -1
  25. package/dist/memory.d.ts +26 -1
  26. package/dist/memory.d.ts.map +1 -1
  27. package/dist/memory.js +44 -7
  28. package/dist/memory.js.map +1 -1
  29. package/dist/physics-config.d.ts +37 -0
  30. package/dist/physics-config.d.ts.map +1 -0
  31. package/dist/physics-config.js +26 -0
  32. package/dist/physics-config.js.map +1 -0
  33. package/dist/physics-state.d.ts +43 -0
  34. package/dist/physics-state.d.ts.map +1 -0
  35. package/dist/physics-state.js +158 -0
  36. package/dist/physics-state.js.map +1 -0
  37. package/dist/physics.d.ts +115 -0
  38. package/dist/physics.d.ts.map +1 -0
  39. package/dist/physics.js +354 -0
  40. package/dist/physics.js.map +1 -0
  41. package/dist/search.d.ts +13 -0
  42. package/dist/search.d.ts.map +1 -1
  43. package/dist/search.js +105 -0
  44. package/dist/search.js.map +1 -1
  45. package/dist/store.d.ts.map +1 -1
  46. package/dist/store.js +14 -5
  47. package/dist/store.js.map +1 -1
  48. package/extensions/openclaw-plugin/README.md +11 -1
  49. package/extensions/openclaw-plugin/index.ts +62 -1
  50. package/extensions/openclaw-plugin/openclaw.plugin.json +1 -1
  51. package/extensions/openclaw-plugin/package.json +1 -1
  52. package/openclaw.plugin.json +1 -1
  53. package/package.json +6 -6
package/README.md CHANGED
@@ -43,10 +43,22 @@ hippo recall "data pipeline issues" --budget 2000
43
43
 
44
44
  That's it. You have a memory system.
45
45
 
46
+ ### What's new in v0.11.1
47
+
48
+ - **OpenClaw error capture filtering.** The `autoLearn` hook now applies three filters before storing tool errors: a noise pattern filter for known transient errors, per-session rate limiting (max 5), and per-session deduplication. Prevents memory pollution from infrastructure noise.
49
+
50
+ ### What's new in v0.11.0
51
+
52
+ - **Reward-proportional decay.** Outcome feedback now modulates decay rate continuously instead of fixed half-life deltas. Memories with consistent positive outcomes decay up to 1.5x slower; consistent negatives decay up to 2x faster. Mixed outcomes converge toward neutral. Inspired by R-STDP in spiking neural networks. `hippo inspect` now shows cumulative outcome counts and the computed reward factor.
53
+ - **Public benchmarks.** Two benchmarks in `benchmarks/`: a [Sequential Learning Benchmark](benchmarks/sequential-learning/) (50 tasks, 10 traps, measures agent improvement over time) and a [LongMemEval integration](benchmarks/longmemeval/) (industry-standard 500-question retrieval benchmark, R@5=74.0% with BM25 only). The sequential learning benchmark is unique: no other public benchmark tests whether memory systems produce learning curves.
54
+
46
55
  ### What's new in v0.10.0
47
56
 
48
57
  - **Active invalidation.** `hippo learn --git` detects migration and breaking-change commits and actively weakens memories referencing the old pattern. Manual invalidation via `hippo invalidate "REST API" --reason "migrated to GraphQL"`.
49
58
  - **Architectural decisions.** `hippo decide` stores one-off decisions with 90-day half-life and verified confidence. Supports `--context` for reasoning and `--supersedes` to chain decisions when the architecture evolves.
59
+ - **Path-based memory triggers.** Memories auto-tagged with `path:<segment>` from your working directory. Recall boosts memories from the same location (up to 1.3x). Working in `src/api/`? API-related memories surface first.
60
+ - **OpenCode integration.** `hippo hook install opencode` patches AGENTS.md. Auto-detected during `hippo init`. Integration guide with MCP config and skill for progressive discovery.
61
+ - **`hippo export`** outputs all memories as JSON or markdown.
50
62
  - **Decision recall boost.** 1.2x scoring multiplier for decision-tagged memories so they surface despite low retrieval frequency.
51
63
 
52
64
  ### What's new in v0.9.1
@@ -421,14 +433,15 @@ hippo recall "why is the gold model broken"
421
433
 
422
434
  hippo outcome --good
423
435
  # Applied positive outcome to 3 memories
424
- # half_life +5d on each
436
+ # reward factor increases, decay slows
425
437
 
426
438
  hippo outcome --bad
427
439
  # Applied negative outcome to 3 memories
428
- # half_life -3d on each
429
- # irrelevant memories decay faster
440
+ # reward factor decreases, decay accelerates
430
441
  ```
431
442
 
443
+ Outcomes are cumulative. A memory with 5 positive outcomes and 0 negative has a reward factor of ~1.42, making its effective half-life 42% longer. A memory with 0 positive and 3 negative has a factor of ~0.63, decaying nearly twice as fast. Mixed outcomes converge toward neutral (1.0).
444
+
432
445
  ---
433
446
 
434
447
  ### Token budgets
@@ -687,34 +700,100 @@ The 7 mechanisms in full: [PLAN.md#core-principles](PLAN.md#core-principles)
687
700
 
688
701
  For how these mechanisms connect to LLM training, continual learning, and open research problems: **[RESEARCH.md](RESEARCH.md)**
689
702
 
690
- **Related work:** [HippoRAG](https://arxiv.org/abs/2405.14831) (Gutierrez et al., 2024) applies hippocampal indexing to RAG via knowledge graphs. Complementary approach HippoRAG optimizes retrieval quality, Hippo optimizes memory lifecycle. Same brain region, different mechanisms.
703
+ **Why does reward modulate decay?** In spiking neural networks, reward-modulated STDP strengthens synapses that contribute to positive outcomes and weakens those that don't. Hippo's reward-proportional decay (v0.11.0) implements this: memories with consistent positive outcomes decay slower, negatives decay faster, with no fixed deltas. Inspired by [MH-FLOCKE](https://github.com/MarcHesse/mhflocke)'s R-STDP architecture for quadruped locomotion, where the same mechanism produces stable learning with 11.6x lower variance than PPO.
704
+
705
+ **Prior art in agent memory simulation.** The idea that human-like memory produces human-like behavior as an emergent property was explored in IEEE research from 2010-2011 ([5952114](https://ieeexplore.ieee.org/document/5952114), [5548405](https://ieeexplore.ieee.org/document/5548405), [5953964](https://ieeexplore.ieee.org/document/5953964)). Walking between rooms and forgetting why you went there doesn't need direct simulation; it emerges naturally from a memory system with capacity limits and decay. Hippo's design follows the same principle: implement the mechanisms, and the behavior follows.
706
+
707
+ **Related work:** [HippoRAG](https://arxiv.org/abs/2405.14831) (Gutierrez et al., 2024) applies hippocampal indexing to RAG via knowledge graphs. [MemPalace](https://github.com/milla-jovovich/mempalace) (Sigman & Jovovich, 2026) organizes memory spatially (wings/halls/rooms) with AAAK compression, achieving 100% on [LongMemEval](https://arxiv.org/abs/2410.10813). [MH-FLOCKE](https://github.com/MarcHesse/mhflocke) (Hesse, 2026) uses spiking neurons with R-STDP for embodied cognition. Each system tackles a different facet: HippoRAG optimizes retrieval quality, MemPalace optimizes retrieval organization, MH-FLOCKE optimizes embodied learning, and Hippo optimizes memory lifecycle.
691
708
 
692
709
  ---
693
710
 
694
711
  ## Comparison
695
712
 
696
- | Feature | Hippo | Mem0 | Basic Memory | Claude-Mem |
697
- |---------|-------|------|-------------|-----------|
713
+ | Feature | Hippo | MemPalace | Mem0 | Basic Memory |
714
+ |---------|-------|-----------|------|-------------|
698
715
  | Decay by default | Yes | No | No | No |
699
716
  | Retrieval strengthening | Yes | No | No | No |
700
- | Hybrid search (BM25 + embeddings) | Yes | Embeddings only | No | No |
717
+ | Reward-proportional decay | Yes | No | No | No |
718
+ | Hybrid search (BM25 + embeddings) | Yes | Embeddings + spatial | Embeddings only | No |
701
719
  | Schema acceleration | Yes | No | No | No |
702
720
  | Conflict detection + resolution | Yes | No | No | No |
703
721
  | Multi-agent shared memory | Yes | No | No | No |
704
722
  | Transfer scoring | Yes | No | No | No |
705
723
  | Outcome tracking | Yes | No | No | No |
706
724
  | Confidence tiers | Yes | No | No | No |
725
+ | Spatial organization | No | Yes (wings/halls/rooms) | No | No |
726
+ | Lossless compression | No | Yes (AAAK, 30x) | No | No |
707
727
  | Cross-tool import | Yes | No | No | No |
708
- | Conversation capture | Yes | No | No | No |
709
728
  | Auto-hook install | Yes | No | No | No |
710
- | MCP server | Yes | No | No | No |
711
- | Native plugins | OpenClaw + Claude Code | No | No | No |
712
- | Multi-repo git learn | Yes | No | No | No |
713
- | Zero dependencies | Yes | No | No | No |
714
- | Git-friendly | Yes | No | Yes | No |
715
- | Framework agnostic | Yes | Partial | Yes | No |
729
+ | MCP server | Yes | Yes | No | No |
730
+ | Zero dependencies | Yes | No (ChromaDB) | No | No |
731
+ | LongMemEval R@5 (retrieval) | 74.0% (BM25 only) | 96.6% (raw) / 100% (reranked) | ~49-85% | N/A |
732
+ | Git-friendly | Yes | No | No | Yes |
733
+ | Framework agnostic | Yes | Yes | Partial | Yes |
734
+
735
+ Different tools answer different questions. Mem0 and Basic Memory implement "save everything, search later." MemPalace implements "store everything, organize spatially for retrieval." Hippo implements "forget by default, earn persistence through use." These are complementary approaches: MemPalace's retrieval precision + Hippo's lifecycle management would be stronger than either alone.
736
+
737
+ ---
738
+
739
+ ## Benchmarks
740
+
741
+ Two benchmarks testing two different things. Full details in [`benchmarks/`](benchmarks/).
742
+
743
+ ### LongMemEval (retrieval accuracy)
744
+
745
+ [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.
746
+
747
+ **Hippo v0.11.0 results (BM25 only, zero dependencies):**
716
748
 
717
- Mem0, Basic Memory, and Claude-Mem all implement "save everything, search later." Hippo implements all 7 hippocampal mechanisms: two-speed storage, decay, retrieval strengthening, schema acceleration, conflict detection, multi-agent transfer, and explicit working memory. It's the only tool that models what memories are worth keeping.
749
+ | Metric | Score |
750
+ |--------|-------|
751
+ | Recall@1 | 50.4% |
752
+ | Recall@3 | 66.6% |
753
+ | Recall@5 | 74.0% |
754
+ | Recall@10 | 82.6% |
755
+ | Answer in content@5 | 46.6% |
756
+
757
+ | Question Type | Count | R@5 |
758
+ |---------------|-------|-----|
759
+ | single-session-assistant | 56 | 94.6% |
760
+ | knowledge-update | 78 | 88.5% |
761
+ | temporal-reasoning | 133 | 73.7% |
762
+ | multi-session | 133 | 72.2% |
763
+ | single-session-user | 70 | 65.7% |
764
+ | single-session-preference | 30 | 26.7% |
765
+
766
+ For context: MemPalace scores 96.6% (raw) using ChromaDB embeddings + spatial indexing. Hippo achieves 74.0% using BM25 keyword matching alone with zero runtime dependencies. Adding embeddings via `hippo embed` (optional `@xenova/transformers` peer dep) enables hybrid search and should close the gap.
767
+
768
+ Hippo's strongest categories (knowledge-update 88.5%, single-session-assistant 94.6%) are the ones where keyword overlap between question and stored content is highest. The weakest (preference 26.7%) involves indirect references that need semantic understanding.
769
+
770
+ ```bash
771
+ cd benchmarks/longmemeval
772
+ python ingest_direct.py --data data/longmemeval_oracle.json --store-dir ./store
773
+ python retrieve_fast.py --data data/longmemeval_oracle.json --store-dir ./store --output results/retrieval.jsonl
774
+ python evaluate_retrieval.py --retrieval results/retrieval.jsonl --data data/longmemeval_oracle.json
775
+ ```
776
+
777
+ ### Sequential Learning Benchmark (agent improvement over time)
778
+
779
+ No other public benchmark tests whether memory systems produce learning curves. LongMemEval tests retrieval on a fixed corpus. This benchmark tests whether an agent with memory *performs better on task 40 than task 5*.
780
+
781
+ 50 tasks, 10 trap categories, each appearing 2-3 times across the sequence.
782
+
783
+ **Hippo v0.11.0 results:**
784
+
785
+ | Condition | Overall | Early | Mid | Late | Learns? |
786
+ |-----------|---------|-------|-----|------|---------|
787
+ | No memory | 100% | 100% | 100% | 100% | No |
788
+ | Static memory | 20% | 33% | 11% | 14% | No |
789
+ | Hippo | 40% | 78% | 22% | 14% | Yes |
790
+
791
+ The hippo agent's trap-hit rate drops from 78% to 14% as it accumulates error memories with 2x half-life. Static pre-loaded memory helps from the start but doesn't improve. Any memory system can run this benchmark by implementing the [adapter interface](benchmarks/sequential-learning/adapters/interface.mjs).
792
+
793
+ ```bash
794
+ cd benchmarks/sequential-learning
795
+ node run.mjs --adapter all
796
+ ```
718
797
 
719
798
  ---
720
799
 
@@ -723,10 +802,13 @@ Mem0, Basic Memory, and Claude-Mem all implement "save everything, search later.
723
802
  Issues and PRs welcome. Before contributing, run `hippo status` in the repo root to see the project's own memory.
724
803
 
725
804
  The interesting problems:
805
+ - **Improve LongMemEval score.** Current R@5 is 74.0% with BM25 only. Adding embeddings (`hippo embed`) and hybrid search should close the gap toward MemPalace's 96.6%.
726
806
  - Better consolidation heuristics (LLM-powered merge vs current text overlap)
727
807
  - Web UI / dashboard for visualizing decay curves and memory health
728
808
  - Optimal decay parameter tuning from real usage data
729
809
  - Cross-agent transfer learning evaluation
810
+ - **MemPalace-style spatial organization.** Could spatial structure (wings/halls/rooms) improve hippo's semantic layer?
811
+ - **AAAK-style compression for semantic memories.** Lossless token compression for context injection.
730
812
 
731
813
  ## License
732
814
 
package/dist/cli.js CHANGED
@@ -28,11 +28,15 @@ import * as path from 'path';
28
28
  import * as fs from 'fs';
29
29
  import * as os from 'os';
30
30
  import { execSync } from 'child_process';
31
- import { createMemory, calculateStrength, deriveHalfLife, resolveConfidence, applyOutcome, computeSchemaFit, Layer, DECISION_HALF_LIFE_DAYS, } from './memory.js';
31
+ import { createMemory, calculateStrength, calculateRewardFactor, deriveHalfLife, resolveConfidence, applyOutcome, computeSchemaFit, Layer, DECISION_HALF_LIFE_DAYS, } from './memory.js';
32
32
  import { getHippoRoot, isInitialized, initStore, writeEntry, readEntry, deleteEntry, loadAllEntries, loadSearchEntries, loadIndex, saveIndex, loadStats, updateStats, saveActiveTaskSnapshot, loadActiveTaskSnapshot, clearActiveTaskSnapshot, appendSessionEvent, listSessionEvents, listMemoryConflicts, resolveConflict, saveSessionHandoff, loadLatestHandoff, loadHandoffById, } from './store.js';
33
- import { markRetrieved, estimateTokens, hybridSearch, explainMatch } from './search.js';
33
+ import { markRetrieved, estimateTokens, hybridSearch, physicsSearch, explainMatch } from './search.js';
34
34
  import { consolidate } from './consolidate.js';
35
35
  import { isEmbeddingAvailable, embedAll, embedMemory, loadEmbeddingIndex, } from './embeddings.js';
36
+ import { loadPhysicsState, resetAllPhysicsState } from './physics-state.js';
37
+ import { computeSystemEnergy, vecNorm } from './physics.js';
38
+ import { loadConfig } from './config.js';
39
+ import { openHippoDb, closeHippoDb } from './db.js';
36
40
  import { captureError, extractLessons, deduplicateLesson, runWatched, fetchGitLog, isGitRepo, } from './autolearn.js';
37
41
  import { extractInvalidationTarget, invalidateMatching } from './invalidation.js';
38
42
  import { extractPathTags } from './path-context.js';
@@ -297,12 +301,26 @@ async function cmdRecall(hippoRoot, query, flags) {
297
301
  const limit = parseLimitFlag(flags['limit']);
298
302
  const asJson = Boolean(flags['json']);
299
303
  const showWhy = Boolean(flags['why']);
304
+ const forcePhysics = Boolean(flags['physics']);
305
+ const forceClassic = Boolean(flags['classic']);
300
306
  const globalRoot = getGlobalRoot();
301
307
  const localEntries = loadSearchEntries(hippoRoot, query);
302
308
  const globalEntries = isInitialized(globalRoot) ? loadSearchEntries(globalRoot, query) : [];
303
309
  const hasGlobal = globalEntries.length > 0;
310
+ // Determine search mode: --physics forces physics, --classic forces BM25+cosine,
311
+ // default uses physics if config.physics.enabled is not false
312
+ const config = loadConfig(hippoRoot);
313
+ const usePhysics = forcePhysics
314
+ || (!forceClassic && config.physics.enabled !== false);
304
315
  let results;
305
- if (hasGlobal) {
316
+ if (usePhysics && !hasGlobal) {
317
+ results = await physicsSearch(query, localEntries, {
318
+ budget,
319
+ hippoRoot,
320
+ physicsConfig: config.physics,
321
+ });
322
+ }
323
+ else if (hasGlobal) {
306
324
  // Use searchBothHybrid for merged results with embedding support
307
325
  results = await searchBothHybrid(query, hippoRoot, globalRoot, { budget });
308
326
  }
@@ -464,8 +482,42 @@ function cmdStatus(hippoRoot) {
464
482
  console.log(`Embeddings: ${embAvail ? 'available' : 'not installed (BM25 only)'}`);
465
483
  if (embAvail) {
466
484
  const embIndex = loadEmbeddingIndex(hippoRoot);
467
- const embCount = Object.keys(embIndex).length;
468
- console.log(`Embedded: ${embCount}/${entries.length} memories`);
485
+ const activeIds = new Set(entries.map((e) => e.id));
486
+ const activeEmbedded = Object.keys(embIndex).filter((id) => activeIds.has(id)).length;
487
+ const orphaned = Object.keys(embIndex).length - activeEmbedded;
488
+ let line = `Embedded: ${activeEmbedded}/${entries.length} memories`;
489
+ if (orphaned > 0)
490
+ line += ` (${orphaned} orphaned — run \`hippo embed\` to prune)`;
491
+ console.log(line);
492
+ }
493
+ // Physics status
494
+ try {
495
+ const db = openHippoDb(hippoRoot);
496
+ try {
497
+ const physicsMap = loadPhysicsState(db);
498
+ if (physicsMap.size > 0) {
499
+ const particles = Array.from(physicsMap.values());
500
+ const physConfig = loadConfig(hippoRoot);
501
+ const energy = computeSystemEnergy(particles, physConfig.physics.G_memory);
502
+ let sumVelMag = 0;
503
+ let maxVelMag = 0;
504
+ for (const p of particles) {
505
+ const mag = vecNorm(p.velocity);
506
+ sumVelMag += mag;
507
+ if (mag > maxVelMag)
508
+ maxVelMag = mag;
509
+ }
510
+ const avgVelMag = sumVelMag / particles.length;
511
+ console.log('');
512
+ console.log(`Physics: ${particles.length} particles, energy: ${fmt(energy.total, 4)} (KE: ${fmt(energy.kinetic, 4)}, PE: ${fmt(energy.potential, 4)}), avg vel: ${fmt(avgVelMag, 4)}`);
513
+ }
514
+ }
515
+ finally {
516
+ closeHippoDb(db);
517
+ }
518
+ }
519
+ catch {
520
+ // Physics table may not exist yet — degrade gracefully
469
521
  }
470
522
  }
471
523
  function cmdOutcome(hippoRoot, flags) {
@@ -533,7 +585,13 @@ function cmdInspect(hippoRoot, id) {
533
585
  console.log(`Schema fit: ${entry.schema_fit}`);
534
586
  console.log(`Pinned: ${entry.pinned}`);
535
587
  console.log(`Tags: ${entry.tags.join(', ') || 'none'}`);
536
- console.log(`Outcome score: ${entry.outcome_score ?? 'none'}`);
588
+ const rewardFactor = calculateRewardFactor(entry);
589
+ const pos = entry.outcome_positive ?? 0;
590
+ const neg = entry.outcome_negative ?? 0;
591
+ const outcomeLabel = pos === 0 && neg === 0
592
+ ? 'none'
593
+ : `+${pos} / -${neg} (reward factor: ${fmt(rewardFactor)})`;
594
+ console.log(`Outcomes: ${outcomeLabel}`);
537
595
  if (entry.conflicts_with.length > 0) {
538
596
  console.log(`Conflicts with: ${entry.conflicts_with.join(', ')}`);
539
597
  }
@@ -1021,7 +1079,12 @@ async function cmdContext(hippoRoot, args, flags) {
1021
1079
  }));
1022
1080
  }
1023
1081
  else {
1024
- results = (await hybridSearch(query, localEntries, { budget, hippoRoot })).map((r) => ({
1082
+ const ctxConfig = loadConfig(hippoRoot);
1083
+ const usePhysicsCtx = ctxConfig.physics?.enabled !== false;
1084
+ const ctxResults = usePhysicsCtx
1085
+ ? await physicsSearch(query, localEntries, { budget, hippoRoot, physicsConfig: ctxConfig.physics })
1086
+ : await hybridSearch(query, localEntries, { budget, hippoRoot });
1087
+ results = ctxResults.map((r) => ({
1025
1088
  entry: r.entry,
1026
1089
  score: r.score,
1027
1090
  tokens: r.tokens,
@@ -1151,11 +1214,29 @@ async function cmdEmbed(hippoRoot, flags) {
1151
1214
  console.log(' npm install @xenova/transformers');
1152
1215
  return;
1153
1216
  }
1217
+ if (flags['reset-physics']) {
1218
+ const entries = loadAllEntries(hippoRoot);
1219
+ const embIndex = loadEmbeddingIndex(hippoRoot);
1220
+ const db = openHippoDb(hippoRoot);
1221
+ try {
1222
+ const count = resetAllPhysicsState(db, entries, embIndex);
1223
+ console.log(`Reset physics state: ${count} particles re-initialized from embeddings.`);
1224
+ }
1225
+ finally {
1226
+ closeHippoDb(db);
1227
+ }
1228
+ return;
1229
+ }
1154
1230
  if (flags['status']) {
1155
1231
  const entries = loadAllEntries(hippoRoot);
1156
1232
  const embIndex = loadEmbeddingIndex(hippoRoot);
1157
- const embCount = Object.keys(embIndex).length;
1158
- console.log(`Embedding status: ${embCount}/${entries.length} memories embedded`);
1233
+ const activeIds = new Set(entries.map((e) => e.id));
1234
+ const activeEmbedded = Object.keys(embIndex).filter((id) => activeIds.has(id)).length;
1235
+ const orphaned = Object.keys(embIndex).length - activeEmbedded;
1236
+ console.log(`Embedding status: ${activeEmbedded}/${entries.length} memories embedded`);
1237
+ if (orphaned > 0) {
1238
+ console.log(` ${orphaned} orphaned embeddings (run \`hippo embed\` to prune)`);
1239
+ }
1159
1240
  const missing = entries.filter((e) => !embIndex[e.id]);
1160
1241
  if (missing.length > 0) {
1161
1242
  console.log(` ${missing.length} memories need embedding (run \`hippo embed\` to embed them)`);
@@ -1164,9 +1245,9 @@ async function cmdEmbed(hippoRoot, flags) {
1164
1245
  }
1165
1246
  console.log('Embedding all memories (this may take a moment on first run to download model)...');
1166
1247
  const count = await embedAll(hippoRoot);
1167
- const entries = loadAllEntries(hippoRoot);
1168
- const embIndex = loadEmbeddingIndex(hippoRoot);
1169
- console.log(`Done. ${count} new embeddings created. ${Object.keys(embIndex).length}/${entries.length} total.`);
1248
+ const entriesAfter = loadAllEntries(hippoRoot);
1249
+ const embIndexAfter = loadEmbeddingIndex(hippoRoot);
1250
+ console.log(`Done. ${count} new embeddings created. ${Object.keys(embIndexAfter).length}/${entriesAfter.length} total.`);
1170
1251
  }
1171
1252
  // ---------------------------------------------------------------------------
1172
1253
  // Watch command