hippo-memory 0.29.3 → 0.30.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 CHANGED
@@ -60,6 +60,19 @@ hippo recall "data pipeline issues" --budget 2000
60
60
 
61
61
  ---
62
62
 
63
+ ### What's new in v0.30.1
64
+
65
+ - **`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.
66
+ - **`hippo status` prints a `Trace:` counter.** The new layer is visible in status output.
67
+ - **`hippo --version` / `-v`** works as expected. Previously errored.
68
+
69
+ ### What's new in v0.30.0
70
+
71
+ - **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.
72
+ - **`hippo recall --outcome success`** — retrieve only successful prior strategies. The missing RSI primitive: "what worked last time I tried this?"
73
+ - **`examples/rsi-demo/`** — a minimal self-improving agent, deterministic and CI-runnable. Uses traces to learn. Current seed: 20% success on tasks 1-10 rising to 100% on tasks 41-50. Non-zero exit if the learning curve collapses — the demo is also the integration test.
74
+ - **Schema v3 migration, with a regression test that preserves existing data.** Idempotent auto-promotion via indexed `source_session_id`. Four inheritance smoke tests lock the claim that traces get decay / search / replay / physics "for free."
75
+
63
76
  ### What's new in v0.29.3
64
77
 
65
78
  - **Post-install banner for Claude Code users.** After `npm install -g hippo-memory`, if Claude Code is detected but the Hippo hook isn't wired yet, a three-line message points the user at `hippo init`. Silent on reinstalls or machines without Claude Code. Opt out via `HIPPO_SKIP_POSTINSTALL=1`.
package/dist/cli.d.ts CHANGED
@@ -11,7 +11,7 @@
11
11
  * hippo outcome --good | --bad [--id <id>]
12
12
  * hippo conflicts [--status <status>] [--json]
13
13
  * hippo snapshot <save|show|clear>
14
- * hippo session <log|show|latest|resume>
14
+ * hippo session <log|show|latest|resume|complete>
15
15
  * hippo handoff <create|latest|show>
16
16
  * hippo current <show>
17
17
  * hippo forget <id>
package/dist/cli.js CHANGED
@@ -11,7 +11,7 @@
11
11
  * hippo outcome --good | --bad [--id <id>]
12
12
  * hippo conflicts [--status <status>] [--json]
13
13
  * hippo snapshot <save|show|clear>
14
- * hippo session <log|show|latest|resume>
14
+ * hippo session <log|show|latest|resume|complete>
15
15
  * hippo handoff <create|latest|show>
16
16
  * hippo current <show>
17
17
  * hippo forget <id>
@@ -28,11 +28,13 @@
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';
34
35
  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';
35
36
  import { markRetrieved, estimateTokens, hybridSearch, physicsSearch, explainMatch, textOverlap } from './search.js';
37
+ import { renderTraceContent, parseSteps } from './trace.js';
36
38
  import { consolidate } from './consolidate.js';
37
39
  import { isEmbeddingAvailable, embedAll, embedMemory, loadEmbeddingIndex, resolveEmbeddingModel, } from './embeddings.js';
38
40
  import { loadPhysicsState, resetAllPhysicsState } from './physics-state.js';
@@ -477,6 +479,32 @@ async function cmdRecall(hippoRoot, query, flags) {
477
479
  budget, hippoRoot, mmr: mmrEnabled, mmrLambda, minResults,
478
480
  });
479
481
  }
482
+ // --outcome filter: drop trace entries whose trace_outcome !== target.
483
+ // Non-trace entries pass through unaffected (traces are the only layer with
484
+ // a meaningful outcome; filtering non-traces by outcome would be incoherent).
485
+ const outcomeFilter = flags['outcome'] !== undefined ? String(flags['outcome']).trim() : '';
486
+ if (outcomeFilter) {
487
+ const validOutcomes = ['success', 'failure', 'partial'];
488
+ if (!validOutcomes.includes(outcomeFilter)) {
489
+ console.error(`Invalid --outcome: "${outcomeFilter}". Must be one of: ${validOutcomes.join(', ')}.`);
490
+ process.exit(1);
491
+ }
492
+ results = results.filter((r) => {
493
+ if (r.entry.layer !== Layer.Trace)
494
+ return true;
495
+ return r.entry.trace_outcome === outcomeFilter;
496
+ });
497
+ }
498
+ // --layer filter: strict, drops entries whose layer does not match.
499
+ const layerFilter = flags['layer'] !== undefined ? String(flags['layer']).trim() : '';
500
+ if (layerFilter) {
501
+ const validLayers = Object.values(Layer);
502
+ if (!validLayers.includes(layerFilter)) {
503
+ console.error(`Invalid --layer: "${layerFilter}". Must be one of: ${validLayers.join(', ')}.`);
504
+ process.exit(1);
505
+ }
506
+ results = results.filter((r) => r.entry.layer === layerFilter);
507
+ }
480
508
  if (limit < results.length) {
481
509
  results = results.slice(0, limit);
482
510
  }
@@ -510,10 +538,13 @@ async function cmdRecall(hippoRoot, query, flags) {
510
538
  tokens: r.tokens,
511
539
  tags: r.entry.tags,
512
540
  content: r.entry.content,
541
+ layer: r.entry.layer,
513
542
  };
543
+ if (r.entry.layer === Layer.Trace) {
544
+ base.trace_outcome = r.entry.trace_outcome;
545
+ }
514
546
  if (showWhy) {
515
547
  const explanation = explainMatch(query, r);
516
- base.layer = r.entry.layer;
517
548
  base.confidence = resolveConfidence(r.entry);
518
549
  base.source = isGlobal ? 'global' : 'local';
519
550
  base.reason = explanation.reason;
@@ -826,6 +857,50 @@ async function cmdEval(hippoRoot, corpusPath, flags) {
826
857
  }
827
858
  }
828
859
  }
860
+ function cmdTraceRecord(hippoRoot, flags) {
861
+ requireInit(hippoRoot);
862
+ const task = String(flags['task'] ?? '').trim();
863
+ const stepsJson = String(flags['steps'] ?? '').trim();
864
+ const outcome = String(flags['outcome'] ?? '').trim();
865
+ const validOutcomes = ['success', 'failure', 'partial'];
866
+ if (!task || !stepsJson || !outcome) {
867
+ console.error('Usage: hippo trace record --task <t> --steps <json> --outcome <success|failure|partial> [--session <id>] [--tag <t>]');
868
+ process.exit(1);
869
+ }
870
+ if (!validOutcomes.includes(outcome)) {
871
+ console.error(`Invalid outcome: "${outcome}". Must be one of: ${validOutcomes.join(', ')}.`);
872
+ process.exit(1);
873
+ }
874
+ let steps;
875
+ try {
876
+ steps = parseSteps(stepsJson);
877
+ }
878
+ catch (err) {
879
+ console.error(String(err instanceof Error ? err.message : err));
880
+ process.exit(1);
881
+ }
882
+ const sessionId = String(flags['session'] ?? '').trim() || null;
883
+ const rawTags = flags['tag'];
884
+ const tags = Array.isArray(rawTags)
885
+ ? rawTags.map((t) => String(t))
886
+ : rawTags !== undefined
887
+ ? [String(rawTags)]
888
+ : [];
889
+ const content = renderTraceContent({
890
+ task,
891
+ steps,
892
+ outcome: outcome,
893
+ });
894
+ const entry = createMemory(content, {
895
+ layer: Layer.Trace,
896
+ tags,
897
+ source: String(flags['source'] ?? 'cli'),
898
+ trace_outcome: outcome,
899
+ source_session_id: sessionId,
900
+ });
901
+ writeEntry(hippoRoot, entry);
902
+ console.log(`Recorded trace ${entry.id} (outcome=${outcome}, ${steps.length} steps)`);
903
+ }
829
904
  function cmdTrace(hippoRoot, id, flags) {
830
905
  requireInit(hippoRoot);
831
906
  const asJson = Boolean(flags['json']);
@@ -1493,6 +1568,7 @@ function cmdStatus(hippoRoot) {
1493
1568
  [Layer.Buffer]: 0,
1494
1569
  [Layer.Episodic]: 0,
1495
1570
  [Layer.Semantic]: 0,
1571
+ [Layer.Trace]: 0,
1496
1572
  };
1497
1573
  const byConfidence = {
1498
1574
  verified: 0,
@@ -1521,6 +1597,7 @@ function cmdStatus(hippoRoot) {
1521
1597
  console.log(` Buffer: ${byLayer[Layer.Buffer]}`);
1522
1598
  console.log(` Episodic: ${byLayer[Layer.Episodic]}`);
1523
1599
  console.log(` Semantic: ${byLayer[Layer.Semantic]}`);
1600
+ console.log(` Trace: ${byLayer[Layer.Trace]}`);
1524
1601
  const conflictCount = listMemoryConflicts(hippoRoot).length;
1525
1602
  console.log(`Pinned: ${pinned}`);
1526
1603
  console.log(`At risk (<0.2): ${atRisk}`);
@@ -1880,6 +1957,32 @@ function cmdSession(hippoRoot, args, flags) {
1880
1957
  printSessionEvents(events);
1881
1958
  return;
1882
1959
  }
1960
+ if (subcommand === 'complete') {
1961
+ const outcome = String(flags['outcome'] ?? '').trim();
1962
+ const summary = String(flags['summary'] ?? '').trim();
1963
+ const validOutcomes = ['success', 'failure', 'partial'];
1964
+ if (!sessionId) {
1965
+ console.error('Usage: hippo session complete --session <session-id> --outcome <success|failure|partial> [--summary "..."]');
1966
+ process.exit(1);
1967
+ }
1968
+ if (!validOutcomes.includes(outcome)) {
1969
+ console.error(`Invalid outcome: "${outcome}". Must be one of: ${validOutcomes.join(', ')}.`);
1970
+ process.exit(1);
1971
+ }
1972
+ const metadata = { ended_at: new Date().toISOString() };
1973
+ if (summary)
1974
+ metadata.summary = summary;
1975
+ const event = appendSessionEvent(hippoRoot, {
1976
+ session_id: sessionId,
1977
+ task: task || null,
1978
+ event_type: 'session_complete',
1979
+ content: outcome,
1980
+ source: String(flags['source'] ?? 'cli'),
1981
+ metadata,
1982
+ });
1983
+ console.log(`Completed session ${event.session_id} with outcome=${outcome} (event #${event.id})`);
1984
+ return;
1985
+ }
1883
1986
  if (subcommand === 'resume') {
1884
1987
  const handoff = loadLatestHandoff(hippoRoot, sessionId || undefined);
1885
1988
  if (!handoff) {
@@ -1910,7 +2013,7 @@ function cmdSession(hippoRoot, args, flags) {
1910
2013
  console.log(lines.join('\n'));
1911
2014
  return;
1912
2015
  }
1913
- console.error('Usage: hippo session <log|show|latest|resume>');
2016
+ console.error('Usage: hippo session <log|show|latest|resume|complete>');
1914
2017
  process.exit(1);
1915
2018
  }
1916
2019
  function printHandoff(handoff) {
@@ -3377,6 +3480,21 @@ Examples:
3377
3480
  const { command, args, flags } = parseArgs(process.argv);
3378
3481
  const hippoRoot = getHippoRoot(process.cwd());
3379
3482
  async function main() {
3483
+ if (command === '--version' || command === '-v' || flags['version']) {
3484
+ const __filename_local = fileURLToPath(import.meta.url);
3485
+ const __dirname_local = path.dirname(__filename_local);
3486
+ const pkgPath = path.join(__dirname_local, '..', 'package.json');
3487
+ let pkgJson;
3488
+ try {
3489
+ pkgJson = fs.readFileSync(pkgPath, 'utf-8');
3490
+ }
3491
+ catch {
3492
+ pkgJson = fs.readFileSync(path.join(__dirname_local, '..', '..', 'package.json'), 'utf-8');
3493
+ }
3494
+ const { version } = JSON.parse(pkgJson);
3495
+ console.log(version);
3496
+ process.exit(0);
3497
+ }
3380
3498
  maybeAutoInstallCodexWrapper(command, args);
3381
3499
  switch (command) {
3382
3500
  case 'init':
@@ -3415,12 +3533,16 @@ async function main() {
3415
3533
  break;
3416
3534
  }
3417
3535
  case 'trace': {
3418
- const id = args[0] ? String(args[0]) : null;
3419
- if (!id) {
3420
- console.error('Usage: hippo trace <memory-id>');
3536
+ const sub = args[0] ? String(args[0]) : '';
3537
+ if (sub === 'record') {
3538
+ cmdTraceRecord(hippoRoot, flags);
3539
+ break;
3540
+ }
3541
+ if (!sub) {
3542
+ console.error('Usage: hippo trace <memory-id> | hippo trace record --task <t> --steps <json> --outcome <o>');
3421
3543
  process.exit(1);
3422
3544
  }
3423
- cmdTrace(hippoRoot, id, flags);
3545
+ cmdTrace(hippoRoot, sub, flags);
3424
3546
  break;
3425
3547
  }
3426
3548
  case 'refine':