hippo-memory 0.29.2 → 0.30.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 CHANGED
@@ -60,6 +60,17 @@ hippo recall "data pipeline issues" --budget 2000
60
60
 
61
61
  ---
62
62
 
63
+ ### What's new in v0.30.0
64
+
65
+ - **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.
66
+ - **`hippo recall --outcome success`** — retrieve only successful prior strategies. The missing RSI primitive: "what worked last time I tried this?"
67
+ - **`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.
68
+ - **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."
69
+
70
+ ### What's new in v0.29.3
71
+
72
+ - **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`.
73
+
63
74
  ### What's new in v0.29.2
64
75
 
65
76
  - **Fix UserPromptSubmit hook in fresh directories.** In v0.29.0/0.29.1, the `hippo context --pinned-only` hook errored with "No .hippo directory found" every time Claude Code opened a session in a cwd without a local hippo store, and would silently auto-create `.hippo/` there. Fixed: pinned-only falls back to global-only, leaves cwd untouched.
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>
@@ -33,6 +33,7 @@ import { installJsonHooks, uninstallJsonHooks, resolveJsonHookPaths, detectInsta
33
33
  import { createMemory, calculateStrength, calculateRewardFactor, deriveHalfLife, resolveConfidence, applyOutcome, computeSchemaFit, Layer, DECISION_HALF_LIFE_DAYS, } from './memory.js';
34
34
  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
35
  import { markRetrieved, estimateTokens, hybridSearch, physicsSearch, explainMatch, textOverlap } from './search.js';
36
+ import { renderTraceContent, parseSteps } from './trace.js';
36
37
  import { consolidate } from './consolidate.js';
37
38
  import { isEmbeddingAvailable, embedAll, embedMemory, loadEmbeddingIndex, resolveEmbeddingModel, } from './embeddings.js';
38
39
  import { loadPhysicsState, resetAllPhysicsState } from './physics-state.js';
@@ -477,6 +478,22 @@ async function cmdRecall(hippoRoot, query, flags) {
477
478
  budget, hippoRoot, mmr: mmrEnabled, mmrLambda, minResults,
478
479
  });
479
480
  }
481
+ // --outcome filter: drop trace entries whose trace_outcome !== target.
482
+ // Non-trace entries pass through unaffected (traces are the only layer with
483
+ // a meaningful outcome; filtering non-traces by outcome would be incoherent).
484
+ const outcomeFilter = flags['outcome'] !== undefined ? String(flags['outcome']).trim() : '';
485
+ if (outcomeFilter) {
486
+ const validOutcomes = ['success', 'failure', 'partial'];
487
+ if (!validOutcomes.includes(outcomeFilter)) {
488
+ console.error(`Invalid --outcome: "${outcomeFilter}". Must be one of: ${validOutcomes.join(', ')}.`);
489
+ process.exit(1);
490
+ }
491
+ results = results.filter((r) => {
492
+ if (r.entry.layer !== Layer.Trace)
493
+ return true;
494
+ return r.entry.trace_outcome === outcomeFilter;
495
+ });
496
+ }
480
497
  if (limit < results.length) {
481
498
  results = results.slice(0, limit);
482
499
  }
@@ -510,10 +527,13 @@ async function cmdRecall(hippoRoot, query, flags) {
510
527
  tokens: r.tokens,
511
528
  tags: r.entry.tags,
512
529
  content: r.entry.content,
530
+ layer: r.entry.layer,
513
531
  };
532
+ if (r.entry.layer === Layer.Trace) {
533
+ base.trace_outcome = r.entry.trace_outcome;
534
+ }
514
535
  if (showWhy) {
515
536
  const explanation = explainMatch(query, r);
516
- base.layer = r.entry.layer;
517
537
  base.confidence = resolveConfidence(r.entry);
518
538
  base.source = isGlobal ? 'global' : 'local';
519
539
  base.reason = explanation.reason;
@@ -826,6 +846,50 @@ async function cmdEval(hippoRoot, corpusPath, flags) {
826
846
  }
827
847
  }
828
848
  }
849
+ function cmdTraceRecord(hippoRoot, flags) {
850
+ requireInit(hippoRoot);
851
+ const task = String(flags['task'] ?? '').trim();
852
+ const stepsJson = String(flags['steps'] ?? '').trim();
853
+ const outcome = String(flags['outcome'] ?? '').trim();
854
+ const validOutcomes = ['success', 'failure', 'partial'];
855
+ if (!task || !stepsJson || !outcome) {
856
+ console.error('Usage: hippo trace record --task <t> --steps <json> --outcome <success|failure|partial> [--session <id>] [--tag <t>]');
857
+ process.exit(1);
858
+ }
859
+ if (!validOutcomes.includes(outcome)) {
860
+ console.error(`Invalid outcome: "${outcome}". Must be one of: ${validOutcomes.join(', ')}.`);
861
+ process.exit(1);
862
+ }
863
+ let steps;
864
+ try {
865
+ steps = parseSteps(stepsJson);
866
+ }
867
+ catch (err) {
868
+ console.error(String(err instanceof Error ? err.message : err));
869
+ process.exit(1);
870
+ }
871
+ const sessionId = String(flags['session'] ?? '').trim() || null;
872
+ const rawTags = flags['tag'];
873
+ const tags = Array.isArray(rawTags)
874
+ ? rawTags.map((t) => String(t))
875
+ : rawTags !== undefined
876
+ ? [String(rawTags)]
877
+ : [];
878
+ const content = renderTraceContent({
879
+ task,
880
+ steps,
881
+ outcome: outcome,
882
+ });
883
+ const entry = createMemory(content, {
884
+ layer: Layer.Trace,
885
+ tags,
886
+ source: String(flags['source'] ?? 'cli'),
887
+ trace_outcome: outcome,
888
+ source_session_id: sessionId,
889
+ });
890
+ writeEntry(hippoRoot, entry);
891
+ console.log(`Recorded trace ${entry.id} (outcome=${outcome}, ${steps.length} steps)`);
892
+ }
829
893
  function cmdTrace(hippoRoot, id, flags) {
830
894
  requireInit(hippoRoot);
831
895
  const asJson = Boolean(flags['json']);
@@ -1493,6 +1557,7 @@ function cmdStatus(hippoRoot) {
1493
1557
  [Layer.Buffer]: 0,
1494
1558
  [Layer.Episodic]: 0,
1495
1559
  [Layer.Semantic]: 0,
1560
+ [Layer.Trace]: 0,
1496
1561
  };
1497
1562
  const byConfidence = {
1498
1563
  verified: 0,
@@ -1880,6 +1945,32 @@ function cmdSession(hippoRoot, args, flags) {
1880
1945
  printSessionEvents(events);
1881
1946
  return;
1882
1947
  }
1948
+ if (subcommand === 'complete') {
1949
+ const outcome = String(flags['outcome'] ?? '').trim();
1950
+ const summary = String(flags['summary'] ?? '').trim();
1951
+ const validOutcomes = ['success', 'failure', 'partial'];
1952
+ if (!sessionId) {
1953
+ console.error('Usage: hippo session complete --session <session-id> --outcome <success|failure|partial> [--summary "..."]');
1954
+ process.exit(1);
1955
+ }
1956
+ if (!validOutcomes.includes(outcome)) {
1957
+ console.error(`Invalid outcome: "${outcome}". Must be one of: ${validOutcomes.join(', ')}.`);
1958
+ process.exit(1);
1959
+ }
1960
+ const metadata = { ended_at: new Date().toISOString() };
1961
+ if (summary)
1962
+ metadata.summary = summary;
1963
+ const event = appendSessionEvent(hippoRoot, {
1964
+ session_id: sessionId,
1965
+ task: task || null,
1966
+ event_type: 'session_complete',
1967
+ content: outcome,
1968
+ source: String(flags['source'] ?? 'cli'),
1969
+ metadata,
1970
+ });
1971
+ console.log(`Completed session ${event.session_id} with outcome=${outcome} (event #${event.id})`);
1972
+ return;
1973
+ }
1883
1974
  if (subcommand === 'resume') {
1884
1975
  const handoff = loadLatestHandoff(hippoRoot, sessionId || undefined);
1885
1976
  if (!handoff) {
@@ -1910,7 +2001,7 @@ function cmdSession(hippoRoot, args, flags) {
1910
2001
  console.log(lines.join('\n'));
1911
2002
  return;
1912
2003
  }
1913
- console.error('Usage: hippo session <log|show|latest|resume>');
2004
+ console.error('Usage: hippo session <log|show|latest|resume|complete>');
1914
2005
  process.exit(1);
1915
2006
  }
1916
2007
  function printHandoff(handoff) {
@@ -3415,12 +3506,16 @@ async function main() {
3415
3506
  break;
3416
3507
  }
3417
3508
  case 'trace': {
3418
- const id = args[0] ? String(args[0]) : null;
3419
- if (!id) {
3420
- console.error('Usage: hippo trace <memory-id>');
3509
+ const sub = args[0] ? String(args[0]) : '';
3510
+ if (sub === 'record') {
3511
+ cmdTraceRecord(hippoRoot, flags);
3512
+ break;
3513
+ }
3514
+ if (!sub) {
3515
+ console.error('Usage: hippo trace <memory-id> | hippo trace record --task <t> --steps <json> --outcome <o>');
3421
3516
  process.exit(1);
3422
3517
  }
3423
- cmdTrace(hippoRoot, id, flags);
3518
+ cmdTrace(hippoRoot, sub, flags);
3424
3519
  break;
3425
3520
  }
3426
3521
  case 'refine':