hippo-memory 1.72.0 → 2.0.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.
Files changed (259) hide show
  1. package/README.md +6 -4
  2. package/dist/agent-memories/apply.js +5 -5
  3. package/dist/api/assemble.js +2 -1
  4. package/dist/api/audit.d.ts +7 -7
  5. package/dist/api/audit.js +19 -17
  6. package/dist/api/auth.d.ts +5 -6
  7. package/dist/api/auth.js +28 -33
  8. package/dist/api/conflicts.js +1 -1
  9. package/dist/api/context-auto.js +4 -2
  10. package/dist/api/context-select.js +8 -20
  11. package/dist/api/context.js +12 -10
  12. package/dist/api/drill-down.js +2 -1
  13. package/dist/api/forget.d.ts +2 -2
  14. package/dist/api/forget.js +6 -7
  15. package/dist/api/graph.js +2 -1
  16. package/dist/api/learn.d.ts +1 -1
  17. package/dist/api/learn.js +45 -44
  18. package/dist/api/memories.d.ts +4 -2
  19. package/dist/api/memories.js +16 -5
  20. package/dist/api/objects.js +2 -1
  21. package/dist/api/outcome.d.ts +5 -5
  22. package/dist/api/outcome.js +16 -15
  23. package/dist/api/predictions.js +2 -1
  24. package/dist/api/promote.d.ts +3 -3
  25. package/dist/api/promote.js +25 -27
  26. package/dist/api/prompt-hook.d.ts +3 -1
  27. package/dist/api/prompt-hook.js +1 -0
  28. package/dist/api/quarantine.js +2 -1
  29. package/dist/api/recall-core.js +8 -3
  30. package/dist/api/recall-pipeline.js +17 -17
  31. package/dist/api/recall-types.d.ts +3 -1
  32. package/dist/api/recall.js +32 -29
  33. package/dist/api/remember-local.d.ts +5 -2
  34. package/dist/api/remember-local.js +14 -6
  35. package/dist/api/remember.d.ts +5 -2
  36. package/dist/api/remember.js +16 -14
  37. package/dist/api/session-end.d.ts +24 -0
  38. package/dist/api/session-end.js +31 -1
  39. package/dist/api/sleep-run.js +5 -4
  40. package/dist/api/sleep.js +4 -3
  41. package/dist/api/tokens.js +1 -1
  42. package/dist/api/types.d.ts +1 -7
  43. package/dist/capture/command.js +1 -1
  44. package/dist/capture/compaction-spool.d.ts +3 -5
  45. package/dist/capture/compaction-spool.js +41 -46
  46. package/dist/capture/session-end-caller.js +1 -1
  47. package/dist/capture/session-worker.js +19 -6
  48. package/dist/cli/audit.js +10 -10
  49. package/dist/cli/auth.js +4 -4
  50. package/dist/cli/client.d.ts +1 -2
  51. package/dist/cli/client.js +13 -13
  52. package/dist/cli/context.js +1 -1
  53. package/dist/cli/curate.d.ts +1 -1
  54. package/dist/cli/curate.js +9 -9
  55. package/dist/cli/decisions.js +4 -5
  56. package/dist/cli/hook-runtime.d.ts +3 -1
  57. package/dist/cli/hook-runtime.js +2 -2
  58. package/dist/cli/init.d.ts +1 -1
  59. package/dist/cli/init.js +9 -9
  60. package/dist/cli/object-verbs.d.ts +2 -2
  61. package/dist/cli/object-verbs.js +4 -4
  62. package/dist/cli/predictions.js +2 -3
  63. package/dist/cli/quality-repair-once.d.ts +1 -1
  64. package/dist/cli/quality-repair-once.js +2 -2
  65. package/dist/cli/refine-llm.js +2 -1
  66. package/dist/cli/remember.js +5 -5
  67. package/dist/cli/serve.js +9 -4
  68. package/dist/cli/session-hooks.d.ts +0 -4
  69. package/dist/cli/session-hooks.js +57 -66
  70. package/dist/cli/setup.d.ts +1 -1
  71. package/dist/cli/setup.js +2 -2
  72. package/dist/cli/shared.d.ts +1 -1
  73. package/dist/cli/shared.js +2 -2
  74. package/dist/cli/sleep.js +2 -2
  75. package/dist/cli/transfer.d.ts +3 -3
  76. package/dist/cli/transfer.js +34 -15
  77. package/dist/cli/usage.d.ts +1 -1
  78. package/dist/cli/usage.js +1 -1
  79. package/dist/cli/verb-row.d.ts +2 -0
  80. package/dist/cli/verbs/health.js +2 -0
  81. package/dist/cli/verbs/memory.js +43 -51
  82. package/dist/cli/verbs/objects.js +6 -3
  83. package/dist/cli/verbs/sessions.js +4 -2
  84. package/dist/cli.js +1 -2
  85. package/dist/connectors/dlq.js +2 -1
  86. package/dist/connectors/github/deletion.js +2 -1
  87. package/dist/connectors/github/ingest.js +2 -1
  88. package/dist/connectors/github/tenant-routing.js +2 -1
  89. package/dist/connectors/github/webhook.js +4 -2
  90. package/dist/connectors/ingest.js +2 -1
  91. package/dist/connectors/slack/deletion.js +2 -1
  92. package/dist/connectors/slack/ingest.js +2 -1
  93. package/dist/connectors/slack/tenant-routing.js +2 -1
  94. package/dist/consolidate/conflicts.js +1 -1
  95. package/dist/consolidate/dag.js +14 -9
  96. package/dist/consolidate/dedupe.js +1 -1
  97. package/dist/consolidate/memory-value.js +1 -1
  98. package/dist/consolidate/merge.js +6 -6
  99. package/dist/consolidate/sleep.js +1 -1
  100. package/dist/consolidate/traces.js +2 -2
  101. package/dist/core/config.js +15 -16
  102. package/dist/core/markdown.js +2 -2
  103. package/dist/core/memory.d.ts +4 -4
  104. package/dist/core/memory.js +10 -5
  105. package/dist/core/physics.d.ts +3 -1
  106. package/dist/core/physics.js +17 -2
  107. package/dist/core/project-identity.js +2 -1
  108. package/dist/core/search-types.d.ts +1 -1
  109. package/dist/dashboard/dashboard-actions.d.ts +2 -2
  110. package/dist/dashboard/dashboard-actions.js +5 -5
  111. package/dist/dashboard/dashboard-queries.js +2 -7
  112. package/dist/dashboard/dashboard-snapshot.js +1 -3
  113. package/dist/dashboard/dashboard.js +16 -5
  114. package/dist/db/connect.d.ts +2 -1
  115. package/dist/db/connect.js +2 -1
  116. package/dist/db/migrate.js +14 -7
  117. package/dist/db/migrations/v11.js +1 -0
  118. package/dist/db/migrations/v14.js +1 -0
  119. package/dist/db/physics-state.d.ts +0 -4
  120. package/dist/db/physics-state.js +5 -31
  121. package/dist/db/request-stores.d.ts +1 -0
  122. package/dist/db/request-stores.js +2 -1
  123. package/dist/db/vector-store.js +2 -1
  124. package/dist/doctor.d.ts +3 -0
  125. package/dist/doctor.js +7 -7
  126. package/dist/embeddings/local.js +2 -2
  127. package/dist/embeddings/provider.js +1 -10
  128. package/dist/entry/store-kit.d.ts +37 -0
  129. package/dist/entry/store-kit.js +25 -0
  130. package/dist/entry/testing.d.ts +6 -0
  131. package/dist/entry/testing.js +7 -0
  132. package/dist/eval/judgment.js +1 -1
  133. package/dist/hooks/json-hooks.js +6 -2
  134. package/dist/importers/core.js +3 -3
  135. package/dist/importers/vault.d.ts +1 -1
  136. package/dist/importers/vault.js +18 -18
  137. package/dist/index.d.ts +2 -2
  138. package/dist/index.js +1 -1
  139. package/dist/learn/autolearn.d.ts +6 -2
  140. package/dist/learn/autolearn.js +8 -6
  141. package/dist/learn/extract.js +1 -1
  142. package/dist/learn/invalidation.d.ts +2 -1
  143. package/dist/learn/invalidation.js +10 -5
  144. package/dist/mcp/memory-tools.d.ts +1 -1
  145. package/dist/mcp/memory-tools.js +6 -9
  146. package/dist/mcp/request.js +20 -19
  147. package/dist/mcp/stdio.js +13 -8
  148. package/dist/rerankers/cross-encoder.js +10 -3
  149. package/dist/search/embed-memories.d.ts +5 -2
  150. package/dist/search/embed-memories.js +40 -28
  151. package/dist/search/engine.d.ts +8 -0
  152. package/dist/search/engine.js +10 -0
  153. package/dist/search/vector.js +5 -5
  154. package/dist/server/auth.d.ts +1 -1
  155. package/dist/server/auth.js +7 -8
  156. package/dist/server/boot.d.ts +2 -1
  157. package/dist/server/boot.js +28 -19
  158. package/dist/server/client-ip.d.ts +9 -4
  159. package/dist/server/client-ip.js +35 -17
  160. package/dist/server/route-table.js +5 -6
  161. package/dist/server/routes/memories.js +4 -2
  162. package/dist/server/sleep-offload.js +3 -3
  163. package/dist/server/tls.d.ts +7 -2
  164. package/dist/server/tls.js +17 -5
  165. package/dist/server/types.d.ts +7 -2
  166. package/dist/server.d.ts +4 -41
  167. package/dist/server.js +3 -30
  168. package/dist/sharing/global-store.d.ts +4 -5
  169. package/dist/sharing/global-store.js +6 -11
  170. package/dist/sharing/global-sync.js +1 -1
  171. package/dist/sharing/search-both.js +9 -2
  172. package/dist/sharing/share.js +2 -4
  173. package/dist/store/ambient.js +2 -1
  174. package/dist/store/candidates.js +18 -8
  175. package/dist/store/cards.js +5 -4
  176. package/dist/store/compactions-record.js +8 -7
  177. package/dist/store/conflicts.d.ts +7 -1
  178. package/dist/store/conflicts.js +6 -5
  179. package/dist/store/connectors/dlq.js +2 -1
  180. package/dist/store/connectors/event-log.d.ts +13 -0
  181. package/dist/store/connectors/event-log.js +24 -0
  182. package/dist/store/connectors/github.js +6 -8
  183. package/dist/store/connectors/slack.js +6 -10
  184. package/dist/store/dashboard-reads.js +1 -0
  185. package/dist/store/delete-and-batch.js +5 -4
  186. package/dist/store/delivery-recorder.d.ts +2 -2
  187. package/dist/store/delivery-recorder.js +1 -6
  188. package/dist/store/descendant-page.js +2 -1
  189. package/dist/store/dormant.js +7 -3
  190. package/dist/store/entry-reads.d.ts +25 -3
  191. package/dist/store/entry-reads.js +43 -12
  192. package/dist/store/entry-row.js +9 -2
  193. package/dist/store/entry-writes.d.ts +10 -7
  194. package/dist/store/entry-writes.js +23 -11
  195. package/dist/store/goals.js +3 -2
  196. package/dist/store/graph-reads.js +5 -4
  197. package/dist/store/graph-view-rows.js +2 -1
  198. package/dist/store/half-life.js +2 -2
  199. package/dist/store/handoffs.d.ts +9 -3
  200. package/dist/store/handoffs.js +4 -3
  201. package/dist/store/index.d.ts +2 -2
  202. package/dist/store/index.js +1 -1
  203. package/dist/store/mirrors.js +9 -4
  204. package/dist/store/open.d.ts +2 -1
  205. package/dist/store/open.js +2 -1
  206. package/dist/store/port.d.ts +59 -3
  207. package/dist/store/quality-repair.js +1 -1
  208. package/dist/store/recall-trace.js +2 -1
  209. package/dist/store/rejected-values.js +14 -9
  210. package/dist/store/rows.js +2 -2
  211. package/dist/store/rule-sql.d.ts +2 -0
  212. package/dist/store/rule-sql.js +12 -6
  213. package/dist/store/search-rows.d.ts +5 -5
  214. package/dist/store/search-rows.js +9 -12
  215. package/dist/store/set-aside.js +1 -1
  216. package/dist/store/sqlite/connector-writes-group.js +3 -1
  217. package/dist/store/sqlite/entry-writes-group.d.ts +3 -3
  218. package/dist/store/sqlite/entry-writes-group.js +1 -1
  219. package/dist/store/sqlite/held-connection.d.ts +2 -0
  220. package/dist/store/sqlite/held-connection.js +1 -1
  221. package/dist/store/sqlite/last-recall-group.d.ts +13 -0
  222. package/dist/store/sqlite/last-recall-group.js +57 -0
  223. package/dist/store/sqlite/local.d.ts +0 -17
  224. package/dist/store/sqlite/local.js +0 -31
  225. package/dist/store/sqlite/maintenance-groups.d.ts +6 -0
  226. package/dist/store/sqlite/maintenance-groups.js +60 -0
  227. package/dist/store/sqlite/objects-group.js +14 -6
  228. package/dist/store/sqlite/store.d.ts +1 -8
  229. package/dist/store/sqlite/store.js +13 -24
  230. package/dist/store/summaries.d.ts +7 -0
  231. package/dist/store/summaries.js +49 -7
  232. package/dist/store/token-ledger-rows.js +3 -2
  233. package/dist/store/vector-index.js +5 -2
  234. package/dist/util/anthropic-messages.d.ts +3 -1
  235. package/dist/util/anthropic-messages.js +4 -2
  236. package/dist/util/atomic-write.d.ts +5 -2
  237. package/dist/util/atomic-write.js +12 -8
  238. package/dist/util/env.d.ts +10 -1
  239. package/dist/util/env.js +10 -1
  240. package/dist/util/format.d.ts +2 -0
  241. package/dist/util/format.js +4 -0
  242. package/dist/util/request-scope.d.ts +6 -2
  243. package/dist/util/request-scope.js +7 -3
  244. package/dist/util/spawn-argv.d.ts +9 -0
  245. package/dist/util/spawn-argv.js +57 -0
  246. package/dist/util/sql.d.ts +3 -0
  247. package/dist/util/sql.js +6 -0
  248. package/dist/util/vector-codec.d.ts +4 -0
  249. package/dist/util/vector-codec.js +15 -0
  250. package/dist/util/version.d.ts +1 -1
  251. package/dist/util/version.js +1 -1
  252. package/extensions/openclaw-plugin/openclaw.plugin.json +1 -1
  253. package/extensions/openclaw-plugin/package.json +1 -1
  254. package/openclaw.plugin.json +1 -1
  255. package/package.json +3 -1
  256. package/dist/api/on-store.d.ts +0 -16
  257. package/dist/api/on-store.js +0 -38
  258. package/dist/cli/lenient-id.d.ts +0 -2
  259. package/dist/cli/lenient-id.js +0 -13
@@ -27,12 +27,31 @@ import { CliExit } from './exit.js';
27
27
  const STDERR_PREVIEW_CHARS = 80;
28
28
  const MAX_ENTRIES_SHOWN = 10;
29
29
  // Watch command
30
- async function cmdWatch(command, hippoRoot, tenantId) {
31
- if (!command) {
32
- printError('Usage: hippo watch "<command>"');
30
+ const WATCH_USAGE = 'Usage: hippo watch -- <command> [args...], or hippo watch --shell "<command line>"';
31
+ /** The argv to run and whether a shell parses it: `--shell` takes one command line, anything else is argv as given. */
32
+ function watchedCommand(args, flags) {
33
+ if (flags['shell'] !== undefined) {
34
+ const line = nonEmptyStringFlag(flags, 'shell');
35
+ if (line === undefined) {
36
+ printError(WATCH_USAGE);
37
+ throw new CliExit(1);
38
+ }
39
+ return { argv: [line], shell: true };
40
+ }
41
+ if (args.length === 0) {
42
+ printError(WATCH_USAGE);
33
43
  throw new CliExit(1);
34
44
  }
35
- const { exitCode, stderr } = await runWatched(command);
45
+ // A quoted 1.x command line would now run as one program name, so it is refused rather than guessed at.
46
+ if (args.length === 1 && /\s/.test(args[0])) {
47
+ printError(`hippo watch: "${args[0]}" is one argument with spaces. Use \`hippo watch -- cmd args\` or \`hippo watch --shell "<command line>"\`.`);
48
+ throw new CliExit(1);
49
+ }
50
+ return { argv: [...args], shell: false };
51
+ }
52
+ async function cmdWatch(argv, shell, hippoRoot, tenantId) {
53
+ const command = argv.join(' ');
54
+ const { exitCode, stderr } = await runWatched(argv, undefined, { shell });
36
55
  if (exitCode === 0) {
37
56
  // Success: no noise
38
57
  return;
@@ -46,7 +65,7 @@ async function cmdWatch(command, hippoRoot, tenantId) {
46
65
  // A rejection-guard refusal of a failed command's output must not crash the watcher:
47
66
  // skip with the message below and still exit with the wrapped command's real exit code.
48
67
  try {
49
- api.rememberLocally(cliApiContext(hippoRoot, tenantId), {
68
+ await api.rememberLocally(cliApiContext(hippoRoot, tenantId), {
50
69
  content: failure.content,
51
70
  tags: failure.tags,
52
71
  layer: failure.layer,
@@ -69,7 +88,7 @@ async function cmdWatch(command, hippoRoot, tenantId) {
69
88
  throw new CliExit(exitCode);
70
89
  }
71
90
  // Learn command
72
- export function handleLearn({ hippoRoot, tenantId, flags }) {
91
+ export async function handleLearn({ hippoRoot, tenantId, flags }) {
73
92
  requireInit(hippoRoot);
74
93
  if (!flags['git']) {
75
94
  printError('Usage: hippo learn --git [--days <n>] [--repos <paths>]');
@@ -84,14 +103,14 @@ export function handleLearn({ hippoRoot, tenantId, flags }) {
84
103
  let totalSkipped = 0;
85
104
  for (const repo of repos) {
86
105
  const label = path.basename(repo);
87
- const { added, skipped } = learnFromRepo(hippoRoot, tenantId, repo, days, label);
106
+ const { added, skipped } = await learnFromRepo(hippoRoot, tenantId, repo, days, label);
88
107
  totalAdded += added;
89
108
  totalSkipped += skipped;
90
109
  }
91
110
  console.log(`Git learn complete: ${totalAdded} new lessons added, ${totalSkipped} duplicates skipped across ${repos.length} repos.`);
92
111
  }
93
112
  else {
94
- const { added, skipped } = learnFromRepo(hippoRoot, tenantId, process.cwd(), days);
113
+ const { added, skipped } = await learnFromRepo(hippoRoot, tenantId, process.cwd(), days);
95
114
  console.log(`Git learn complete: ${added} new lessons added, ${skipped} duplicates skipped.`);
96
115
  }
97
116
  }
@@ -118,7 +137,7 @@ function warnRedacted(count) {
118
137
  if (count)
119
138
  printError(`Warning: secret-shaped text was redacted from ${count} imported ${count === 1 ? 'entry' : 'entries'} before storing`);
120
139
  }
121
- export function handleImport({ hippoRoot, tenantId, args, flags }) {
140
+ export async function handleImport({ hippoRoot, tenantId, args, flags }) {
122
141
  const useGlobal = boolFlag(flags, 'global');
123
142
  const dryRun = boolFlag(flags, 'dry-run');
124
143
  const extraTags = Array.isArray(flags['tag'])
@@ -144,7 +163,7 @@ export function handleImport({ hippoRoot, tenantId, args, flags }) {
144
163
  // Vault import is a FOLDER importer that dispatches apart from the single-file slot below; it writes through tenant-scoped api calls,
145
164
  // so the tenant is resolved and passed through. --global is unsupported (the connector raw-archive path is tenant-local).
146
165
  if (flags['vault'])
147
- return importVaultFolder(hippoRoot, tenantId, flags, importOptions, useGlobal, dryRun);
166
+ return await importVaultFolder(hippoRoot, tenantId, flags, importOptions, useGlobal, dryRun);
148
167
  importFromFile(targetRoot, args, flags, { importOptions, useGlobal, dryRun, tenantId });
149
168
  }
150
169
  // Determine which importer to use based on flag
@@ -219,7 +238,7 @@ function importAgentMemories(hippoRoot, useGlobal, dryRun) {
219
238
  for (const warning of report.warnings)
220
239
  printError(`hippo: agent memories: ${warning}`);
221
240
  }
222
- function importVaultFolder(hippoRoot, tenantId, flags, importOptions, useGlobal, dryRun) {
241
+ async function importVaultFolder(hippoRoot, tenantId, flags, importOptions, useGlobal, dryRun) {
223
242
  const folderPath = String(flags['vault']);
224
243
  checkVaultArgs(folderPath, flags, useGlobal);
225
244
  const vaultOptions = {
@@ -228,7 +247,7 @@ function importVaultFolder(hippoRoot, tenantId, flags, importOptions, useGlobal,
228
247
  name: flags['name'] ? String(flags['name']) : undefined,
229
248
  scope: flags['scope'] ? String(flags['scope']) : undefined,
230
249
  };
231
- const vaultResult = importVault(folderPath, vaultOptions);
250
+ const vaultResult = await importVault(folderPath, vaultOptions);
232
251
  printVaultSummary(vaultResult, folderPath, cliApiContext(hippoRoot, tenantId), dryRun);
233
252
  }
234
253
  function checkVaultArgs(folderPath, flags, useGlobal) {
@@ -300,9 +319,9 @@ export function handleSync({ hippoRoot, flags }) {
300
319
  console.log(`Synced ${count} global memories into local project.${includeCrossProject ? '' : ' (other-project rows skipped; use --cross-project to ' +
301
320
  'include them)'}`);
302
321
  }
303
- export async function handleWatch({ hippoRoot, tenantId, args }) {
304
- const watchCmd = args.join(' ').trim();
305
- await cmdWatch(watchCmd, hippoRoot, tenantId);
322
+ export async function handleWatch({ hippoRoot, tenantId, args, flags }) {
323
+ const { argv, shell } = watchedCommand(args, flags);
324
+ await cmdWatch(argv, shell, hippoRoot, tenantId);
306
325
  }
307
326
  export async function handlePromote({ hippoRoot, tenantId, args }) {
308
327
  const id = args[0];
@@ -2,5 +2,5 @@ export declare function printAuditPruneUsage(): void;
2
2
  export declare function printSlackBackfillUsage(): void;
3
3
  export declare function printSlackWorkspacesUsage(): void;
4
4
  export declare const USAGE_HEADER = "\nHippo - Make your agent's memory work like a brain. Hippo is long-term memory for coding agents.\n\nUsage: hippo <command> [options]\n\nCommands:";
5
- export declare const USAGE_EXAMPLES = "\n\nExamples:\n hippo init\n hippo remember \"FRED cache can silently drop series\" --tag error\n hippo recall \"data pipeline issues\" --budget 2000\n hippo context --auto --budget 1500\n hippo conflicts\n hippo reject mem_abc123 --reason \"leaked credential\"\n hippo reject --value \"never store my key again\" --reason \"secret\"\n hippo rejections\n hippo unreject a1b2c3d4e5f6\n hippo dormant \"staging hostname\"\n hippo dormant restore mem_abc123\n hippo tokens --days 7\n hippo session log --id sess_123 --task \"Ship feature\" --type progress --content \"Build is green, next step is docs\"\n hippo session latest --json\n hippo session resume\n hippo snapshot save --task \"Ship feature\" --summary \"Tests are green\" --next-step \"Open the PR\" --session sess_123\n hippo handoff create --summary \"PR is open, tests green\" --next \"Merge after review\" --session sess_123 --artifact src/foo.ts\n hippo handoff create --summary s --constraint a --constraint b --outcome partial --target-runtime codex --card-id c1 --tests pass\n hippo card create --title \"Add cards table\" --repo hippo --depends-on card_abc\n hippo card claim card_abc --runtime codex\n hippo card complete card_abc --outcome success\n hippo card heartbeat card_abc --run 7\n hippo card reclaim\n hippo embed --status\n hippo watch \"npm run build\"\n hippo learn --git --days 30\n hippo promote mem_abc123\n hippo sync\n hippo setup\n hippo hook install claude-code\n hippo decide \"Use PostgreSQL for new services\" --context \"JSONB support\"\n hippo incident \"Prod outage: DB connection pool exhausted\" --context \"spike at 14:00\"\n hippo process new \"Release\" --step \"run tests\" --step \"bump version\" --step \"publish\"\n hippo policy new \"Data retention\" --text \"Delete logs after 90 days\" --from 2026-01-01\n hippo policy asof 2026-03-01 --name \"Data retention\"\n hippo skill new \"Run tests\" --instructions \"npm test before every commit\" --trigger \"before commit\"\n hippo skill export\n hippo brief new \"hippo\" --summary \"Agent-memory library; E2 first-class objects in progress\"\n hippo brief refresh \"hippo\"\n hippo note new \"Acme Corp\" --text \"Renewal call: wants SSO before Q3; champion is the VP Eng\"\n hippo note list --customer \"Acme Corp\" --status active\n hippo graph extract\n hippo invalidate \"REST API\" --dry-run\n hippo invalidate \"REST API\" --reason \"migrated to GraphQL\"\n hippo invalidate --id mem_a1b2c3d4e5f6 --reason \"superseded by new policy\"\n hippo invalidate --churn --dry-run\n hippo export memories.json\n hippo export --format markdown memories.md\n hippo sleep --dry-run\n hippo outcome --good\n hippo status\n";
5
+ export declare const USAGE_EXAMPLES = "\n\nExamples:\n hippo init\n hippo remember \"FRED cache can silently drop series\" --tag error\n hippo recall \"data pipeline issues\" --budget 2000\n hippo context --auto --budget 1500\n hippo conflicts\n hippo reject mem_abc123 --reason \"leaked credential\"\n hippo reject --value \"never store my key again\" --reason \"secret\"\n hippo rejections\n hippo unreject a1b2c3d4e5f6\n hippo dormant \"staging hostname\"\n hippo dormant restore mem_abc123\n hippo tokens --days 7\n hippo session log --id sess_123 --task \"Ship feature\" --type progress --content \"Build is green, next step is docs\"\n hippo session latest --json\n hippo session resume\n hippo snapshot save --task \"Ship feature\" --summary \"Tests are green\" --next-step \"Open the PR\" --session sess_123\n hippo handoff create --summary \"PR is open, tests green\" --next \"Merge after review\" --session sess_123 --artifact src/foo.ts\n hippo handoff create --summary s --constraint a --constraint b --outcome partial --target-runtime codex --card-id c1 --tests pass\n hippo card create --title \"Add cards table\" --repo hippo --depends-on card_abc\n hippo card claim card_abc --runtime codex\n hippo card complete card_abc --outcome success\n hippo card heartbeat card_abc --run 7\n hippo card reclaim\n hippo embed --status\n hippo watch -- npm run build\n hippo learn --git --days 30\n hippo promote mem_abc123\n hippo sync\n hippo setup\n hippo hook install claude-code\n hippo decide \"Use PostgreSQL for new services\" --context \"JSONB support\"\n hippo incident \"Prod outage: DB connection pool exhausted\" --context \"spike at 14:00\"\n hippo process new \"Release\" --step \"run tests\" --step \"bump version\" --step \"publish\"\n hippo policy new \"Data retention\" --text \"Delete logs after 90 days\" --from 2026-01-01\n hippo policy asof 2026-03-01 --name \"Data retention\"\n hippo skill new \"Run tests\" --instructions \"npm test before every commit\" --trigger \"before commit\"\n hippo skill export\n hippo brief new \"hippo\" --summary \"Agent-memory library; E2 first-class objects in progress\"\n hippo brief refresh \"hippo\"\n hippo note new \"Acme Corp\" --text \"Renewal call: wants SSO before Q3; champion is the VP Eng\"\n hippo note list --customer \"Acme Corp\" --status active\n hippo graph extract\n hippo invalidate \"REST API\" --dry-run\n hippo invalidate \"REST API\" --reason \"migrated to GraphQL\"\n hippo invalidate --id mem_a1b2c3d4e5f6 --reason \"superseded by new policy\"\n hippo invalidate --churn --dry-run\n hippo export memories.json\n hippo export --format markdown memories.md\n hippo sleep --dry-run\n hippo outcome --good\n hippo status\n";
6
6
  //# sourceMappingURL=usage.d.ts.map
package/dist/cli/usage.js CHANGED
@@ -50,7 +50,7 @@ Examples:
50
50
  hippo card heartbeat card_abc --run 7
51
51
  hippo card reclaim
52
52
  hippo embed --status
53
- hippo watch "npm run build"
53
+ hippo watch -- npm run build
54
54
  hippo learn --git --days 30
55
55
  hippo promote mem_abc123
56
56
  hippo sync
@@ -18,6 +18,8 @@ export interface VerbRow {
18
18
  readonly aliases?: readonly string[];
19
19
  /** Runs in a request scope, opening each store once; set on api-backed verbs, as hook verbs open their own. */
20
20
  readonly scoped?: true;
21
+ /** Routine verbs after which a previously installed codex wrapper is repaired (never first-installed). */
22
+ readonly repairsCodexWrapper?: true;
21
23
  /** Deletes or hides memories: an unknown flag stops the run instead of being ignored. */
22
24
  readonly destructive?: true;
23
25
  /** Unset: --dry-run is honoured exactly when the verb declares it; `false` refuses it even so. */
@@ -4,6 +4,7 @@ import { DEFAULT_ASSEMBLE_BUDGET } from '../../api/assemble.js';
4
4
  import { verb } from '../verb-row.js';
5
5
  export const HEALTH_VERBS = {
6
6
  status: verb(() => import('../status.js'), 'handleStatus', {
7
+ repairsCodexWrapper: true,
7
8
  flags: {},
8
9
  usage: [`
9
10
  status Show memory health stats`],
@@ -98,6 +99,7 @@ export const HEALTH_VERBS = {
98
99
  --json Output as JSON`],
99
100
  }),
100
101
  outcome: verb(() => import('../curate.js'), 'handleOutcome', {
102
+ repairsCodexWrapper: true,
101
103
  scoped: true,
102
104
  flags: { switches: ['bad', 'good'], values: ['id'] },
103
105
  usage: [`
@@ -5,6 +5,7 @@ import { DEFAULT_GRAPH_HOPS, DEFAULT_GRAPH_SEED_COUNT } from '../../graph/stream
5
5
  import { verb } from '../verb-row.js';
6
6
  export const MEMORY_VERBS = {
7
7
  init: verb(() => import('../init.js'), 'handleInit', {
8
+ repairsCodexWrapper: true,
8
9
  flags: { switches: ['global', 'no-hooks', 'no-learn', 'no-schedule'], values: ['scan'], numbers: ['days'] },
9
10
  usage: [`
10
11
  init Create .hippo/ structure in current directory
@@ -18,6 +19,7 @@ export const MEMORY_VERBS = {
18
19
  coding agents' own memories (every init imports those)`],
19
20
  }),
20
21
  remember: verb(() => import('../remember.js'), 'handleRemember', {
22
+ repairsCodexWrapper: true,
21
23
  flags: {
22
24
  switches: ['error', 'extract', 'force', 'global', 'inferred', 'observed', 'pin', 'verified'],
23
25
  values: ['artifact-ref', 'kind', 'layer', 'owner', 'scope'],
@@ -43,6 +45,7 @@ export const MEMORY_VERBS = {
43
45
  --pin Pin the new memory (default: pinned if the old one was)`],
44
46
  }),
45
47
  recall: verb(() => import('../recall.js'), 'handleRecall', {
48
+ repairsCodexWrapper: true,
46
49
  scoped: true,
47
50
  flags: {
48
51
  switches: ['classic', 'continuity', 'equal-sources', 'evc-adaptive', 'filter-conflicts', 'graph-stream',
@@ -61,47 +64,40 @@ export const MEMORY_VERBS = {
61
64
  --as-of <iso-date> Return the memories that were current on that date
62
65
  --multihop Search a second time with the speaker: and topic: tags the
63
66
  first pass found (also on when config multihop.enabled is true)
64
- --hops <n> E3.2 multi-hop graph recall: also surface memories
65
- reached by walking the entities/relations graph <n>
66
- hops (0..3, default off) out from the lexical seeds.
67
- Graph hits are tagged [graph: Nhop <rel>]. Today the
68
- graph holds supersedes edges (E3.1); cross-object edges
69
- light up the same traversal once extracted.
67
+ --hops <n> Also surface memories reached by walking the entity and
68
+ relation graph <n> hops (0..3, default off) out from the
69
+ lexical matches. Graph hits are tagged [graph: Nhop <rel>].
70
+ Today the graph holds supersedes edges; cross-object
71
+ edges light up the same walk once extracted.
70
72
  --max-neighbors <n> Per-hop fanout cap for --hops (1..200, default 25).
71
- --graph-stream L1: fuse a graph-retrieval stream into RRF, re-ranking
72
- in-pool results by graph proximity to the strong lexical
73
- seeds. Implies rrf scoring (default is blend). Local store
74
- only. Distinct from --hops (which injects out-of-pool
75
- neighbours); this re-ranks within the candidate pool.
73
+ --graph-stream Fuse a graph-proximity ranking into the result list,
74
+ re-ranking in-pool results by closeness to the strong
75
+ lexical matches. Implies rrf scoring (default is blend).
76
+ Local store only. Unlike --hops, it adds no memories
77
+ from outside the candidate pool.
76
78
  --graph-hops <n> Hops for --graph-stream (1..3, default ${DEFAULT_GRAPH_HOPS}).
77
79
  --graph-seeds <n> Lexical anchors for --graph-stream (default ${DEFAULT_GRAPH_SEED_COUNT}). The stream
78
- re-ranks the rank>seeds tail; on a pool with <= n candidates
79
- every candidate is a seed and the stream is inert.
80
+ re-ranks the results below the anchors; on a pool with <= n
81
+ candidates every candidate is an anchor and the stream is inert.
80
82
  --no-mmr Disable MMR diversity re-ranking
81
83
  --mmr-lambda <f> MMR balance 0..1 (default: ${DEFAULT_MMR_LAMBDA}, 1.0 = pure relevance)
82
- --evc-adaptive ACC-style: when top-K shows high inter-item overlap
83
- (= conflict cluster), expand pool and re-rank by
84
- recency. Default off. RESEARCH.md §PFC.ACC.
85
- --filter-conflicts vlPFC interference filter: drop superseded entries
86
- and 0.3x-downweight entries flagged in an open
87
- conflict with a peer in the same result set.
88
- Uses recorded supersession + conflicts only — never
89
- lexical inference. Default off. RESEARCH.md §PFC.vlPFC.
90
- --value-aware vmPFC value attribution: boost memories with positive
91
- cumulative outcomes and demote those with negative
92
- outcomes during ranking. Multiplier
93
- clip(1 + 0.3*tanh(pos - neg), 0.7, 1.3). Reuses
94
- outcome_positive / outcome_negative; no schema
95
- change. Default off. RESEARCH.md §PFC.vmPFC.
96
- --rerank-utility OFC option-value re-ranker: combine relevance,
97
- strength, and integration cost into a single utility
98
- = score * (0.5 + 0.5 * strength) * (1 - cost_factor)
99
- where cost_factor = min(0.3, tokens / 10000). Re-sorts
100
- results by utility. Default off. RESEARCH.md §PFC.OFC.
84
+ --evc-adaptive When the top results overlap heavily (a conflict
85
+ cluster), widen the pool and re-rank by recency.
86
+ Default off.
87
+ --filter-conflicts Drop superseded entries and down-weight by 0.3x any
88
+ entry in an open conflict with another result.
89
+ Uses recorded supersession and conflicts only, never
90
+ lexical inference. Default off.
91
+ --value-aware Boost memories with positive cumulative outcomes and
92
+ demote those with negative ones during ranking:
93
+ multiplier clip(1 + 0.3*tanh(pos - neg), 0.7, 1.3).
94
+ Reads outcome_positive and outcome_negative. Default off.
95
+ --rerank-utility Re-sort results by utility = score * (0.5 + 0.5 * strength)
96
+ * (1 - cost_factor), where cost_factor =
97
+ min(0.3, tokens / 10000). Default off.
101
98
  --reranker <name> Apply a reranker pass after retrieval
102
- (cross-encoder|jev|clef-flash|clef|llm). Looks up
103
- the named reranker from src/rerankers/index.ts and
104
- re-orders the top-K candidates. Default unset (no reranker).
99
+ (cross-encoder|jev|clef-flash|clef|llm) to re-order the
100
+ top-K candidates. Default unset (no reranker).
105
101
  jev calls the hosted TypeSafe Jev API: it needs
106
102
  TYPESAFE_API_KEY, sends the query and candidate
107
103
  text to that API, costs about 0.0004 USD a recall,
@@ -110,30 +106,24 @@ export const MEMORY_VERBS = {
110
106
  Cloudflare Workers AI (CLOUDFLARE_ACCOUNT_ID and
111
107
  CLOUDFLARE_API_TOKEN) or to HIPPO_CLEF_ENDPOINT, and
112
108
  keep the native order on any failure.
113
- See docs/evals/2026-09-19-jev-reranker.md and
114
- docs/plans/2026-05-10-f6-reranker-hardening.md.
115
109
  --reranker-top-k <n> Cap candidates passed to the reranker (default 50;
116
110
  40 for jev, clef-flash and clef).
117
- --goal <tag> dlPFC goal-conditioned recall: memories tagged with
118
- the goal tag get a 1.5x score boost and results are
119
- re-sorted. Default off. RESEARCH.md §PFC.dlPFC.
120
- --session-id <id> Session identifier for dlPFC goal-stack boost.
111
+ --goal <tag> Goal-conditioned recall: memories tagged with the
112
+ goal tag get a 1.5x score boost and results are
113
+ re-sorted. Default off.
114
+ --session-id <id> Session identifier for the goal-stack boost.
121
115
  Defaults to $HIPPO_SESSION_ID. When set and the
122
116
  (tenant, session) has active goals (see
123
117
  'hippo goal push'), recall auto-boosts memories
124
118
  whose tags match an active goal name. Boost stacks
125
119
  on top of base BM25 score, capped at 3.0x.
126
120
  --salience-threshold <n>
127
- Pineal salience: down-weight memories whose
128
- retrieval_count is below n. score *= max(0.5,
129
- retrieval_count / n) for entries with count < n;
130
- entries at or above n are unchanged. Salience emerges
131
- from USE, not from lexical overlap. Default off.
132
- RESEARCH.md §"AI Pineal Gland". (v1's creation-time
133
- lexical gate destroyed LoCoMo 0.28 -> 0.02; this v2
134
- is retrieval-side, opt-in only — see MEMORY.md
135
- "Hippo salience gate destroys benchmark recall".)
136
- --continuity Include continuity block (active task snapshot,
121
+ Down-weight memories retrieved fewer than n times:
122
+ score *= max(0.5, retrieval_count / n) for entries
123
+ with count < n; entries at or above n are unchanged.
124
+ Salience comes from use, not from lexical overlap.
125
+ Default off.
126
+ --continuity Include a continuity block (active task snapshot,
137
127
  latest matching session handoff, last 5 session
138
128
  events) above the memory list. Useful at agent
139
129
  boot when you want both relevant memories AND
@@ -198,6 +188,7 @@ export const MEMORY_VERBS = {
198
188
  --json Output full summary as JSON`],
199
189
  }),
200
190
  context: verb(() => import('../context.js'), 'handleContext', {
191
+ repairsCodexWrapper: true,
201
192
  flags: {
202
193
  switches: ['auto', 'cross-project', 'pinned-only'],
203
194
  values: ['budget', 'format', 'framing', 'include-recent', 'runtime', 'scope'],
@@ -215,6 +206,7 @@ export const MEMORY_VERBS = {
215
206
  --framing <mode> Framing: observe (default), suggest, assert`],
216
207
  }),
217
208
  sleep: verb(() => import('../sleep.js'), 'handleSleep', {
209
+ repairsCodexWrapper: true,
218
210
  destructive: true,
219
211
  scoped: true,
220
212
  flags: { switches: ['dry-run', 'no-learn', 'no-share'], values: ['log-file'] },
@@ -188,16 +188,19 @@ export const OBJECT_VERBS = {
188
188
  mcp Start MCP server (stdio transport)`],
189
189
  }),
190
190
  serve: verb(() => import('../serve.js'), 'handleServe', {
191
- flags: { values: ['host', 'tls-cert', 'tls-key'], numbers: ['port'] },
191
+ flags: { switches: ['allow-cleartext'], values: ['host', 'tls-cert', 'tls-key'], numbers: ['port'] },
192
192
  usage: [`
193
193
  serve Start the HTTP API server for this store (Ctrl+C stops it)
194
194
  --port <n> Port to serve on (default: $HIPPO_PORT or ${DEFAULT_SERVER_PORT})
195
195
  --host <host> Address to bind (default: ${DEFAULT_SERVER_HOST})
196
196
  --tls-cert <file> PEM certificate; serve HTTPS only (or $HIPPO_TLS_CERT)
197
197
  --tls-key <file> PEM private key for it; give both (or $HIPPO_TLS_KEY)
198
+ --allow-cleartext Bind a non-loopback host without TLS, for a TLS proxy
199
+ in front (or $HIPPO_ALLOW_CLEARTEXT=1)
198
200
  Every request needs an API key (hippo auth create).
199
201
  $HIPPO_ALLOW_KEYLESS_LOCAL=1 lets requests from this machine
200
- in without one; $HIPPO_REQUIRE_AUTH=1 wins over it.`],
202
+ in without one, as a member ($HIPPO_KEYLESS_LOCAL_ROLE=admin
203
+ for host admin); $HIPPO_REQUIRE_AUTH=1 wins over it.`],
201
204
  }),
202
205
  goal: verb(() => import('../goals.js'), 'handleGoal', {
203
206
  scoped: true,
@@ -206,7 +209,7 @@ export const OBJECT_VERBS = {
206
209
  values: ['level', 'outcome', 'parent', 'policy', 'session-id', 'success', 'tenant-id'],
207
210
  },
208
211
  usage: [`
209
- goal <sub> dlPFC goal stack (B3) — scoped per session
212
+ goal <sub> Goal stack, scoped per session
210
213
  goal push <name> Push a new active goal; prints the new goal id
211
214
  --policy <type> schema-fit-biased | error-prioritized |
212
215
  recency-first | hybrid
@@ -132,9 +132,10 @@ export const SESSION_VERBS = {
132
132
  --status Show embedding coverage`],
133
133
  }),
134
134
  watch: verb(() => import('../transfer.js'), 'handleWatch', {
135
- flags: {},
135
+ flags: { values: ['shell'] },
136
136
  usage: [`
137
- watch "<command>" Run command, auto-learn from failures`],
137
+ watch -- <cmd> [args] Run command, auto-learn from failures (no shell)
138
+ --shell "<line>" Run one command line through the shell instead`],
138
139
  }),
139
140
  learn: verb(() => import('../transfer.js'), 'handleLearn', {
140
141
  scoped: true,
@@ -203,6 +204,7 @@ export const SESSION_VERBS = {
203
204
  --format <fmt> Output format: json (default) or markdown`],
204
205
  }),
205
206
  capture: verb(() => import('../session-hooks.js'), 'handleCapture', {
207
+ repairsCodexWrapper: true,
206
208
  flags: { switches: ['dry-run', 'global', 'last-session', 'stdin'], values: ['file', 'log-file', 'transcript'] },
207
209
  usage: [`
208
210
  capture Extract memories from conversation text
package/dist/cli.js CHANGED
@@ -107,9 +107,8 @@ export function parseArgs(argv) {
107
107
  export function shouldAutoRepairCodexWrapper(currentCommand, flags) {
108
108
  if (envSkipAutoIntegrations())
109
109
  return false;
110
- if (!['context', 'remember', 'recall', 'sleep', 'capture', 'outcome', 'status', 'init'].includes(currentCommand)) {
110
+ if (!COMMAND_INDEX.get(currentCommand)?.repairsCodexWrapper)
111
111
  return false;
112
- }
113
112
  if (currentCommand === 'init' && flagIsTrue(flags, 'no-hooks'))
114
113
  return false;
115
114
  return true;
@@ -1,7 +1,8 @@
1
1
  // The dead-letter queue every webhook connector parks into: one redaction, one unroutable sentinel, one set of defaults, one replay result.
2
2
  import { DEFAULT_LIST_LIMIT } from '../util/limits.js';
3
3
  import { DLQ_REDACTED_NOTE, redactPayload } from '../util/secret-detect.js';
4
- import { requireGroup, storeFor } from '../store/index.js';
4
+ import { requireGroup } from '../store/index.js';
5
+ import { storeFor } from '../store/sqlite/store.js';
5
6
  import { errorMessage } from '../util/log.js';
6
7
  /** The tenant stored on a row no tenant was resolved for; the column is NOT NULL. */
7
8
  const UNROUTABLE_TENANT = '__unroutable__';
@@ -1,4 +1,5 @@
1
- import { requireGroup, storeFor } from '../../store/index.js';
1
+ import { requireGroup } from '../../store/index.js';
2
+ import { storeFor } from '../../store/sqlite/store.js';
2
3
  /** Handle `issue_comment.deleted` and `pull_request_review_comment.deleted`: archive ALL active raw rows for the artifact_ref in one write.
3
4
  * Tenant and kind='raw' filtering are load-bearing: without them a deletion could archive another tenant's row or a distilled row. */
4
5
  export async function handleCommentDeleted(ctx, input) {
@@ -1,7 +1,8 @@
1
1
  // GitHub event ingest, once per idempotency key. The key comes from the event's own content, never from the unsigned
2
2
  // X-GitHub-Delivery header, so a replay cannot get past it by rotating the delivery id. The pre-check is only the fast path:
3
3
  // the store logs the key in the memory's own transaction, which is what holds when two workers race.
4
- import { requireGroup, storeFor } from '../../store/index.js';
4
+ import { requireGroup } from '../../store/index.js';
5
+ import { storeFor } from '../../store/sqlite/store.js';
5
6
  import { rememberWithEventLog } from '../ingest.js';
6
7
  import { computeIdempotencyKey } from './signature.js';
7
8
  import { issueEventToRememberOpts, issueCommentEventToRememberOpts, pullRequestEventToRememberOpts, prReviewCommentEventToRememberOpts, } from './transform.js';
@@ -1,5 +1,6 @@
1
1
  import { envGitHubAllowUnknownInstallationFallback, envTenant } from '../../util/env.js';
2
- import { requireGroup, storeFor } from '../../store/index.js';
2
+ import { requireGroup } from '../../store/index.js';
3
+ import { storeFor } from '../../store/sqlite/store.js';
3
4
  /** Resolve a GitHub envelope's tenant_id by installation id, then repository.full_name; the HIPPO_TENANT fallback applies only if both tables are empty.
4
5
  * An unknown installation or repo on a multi-tenant deployment returns null (fail closed); GITHUB_ALLOW_UNKNOWN_INSTALLATION_FALLBACK=1 escapes. */
5
6
  export async function resolveTenantForGitHub(hippoRoot, args, store) {
@@ -1,4 +1,5 @@
1
1
  import { envGitHubWebhookSecret, envGitHubWebhookSecretPrevious } from '../../util/env.js';
2
+ import { DAY_MS } from '../../util/time.js';
2
3
  import { verifyGitHubSignature } from './signature.js';
3
4
  import { isGitHubWebhookEnvelope, isGitHubIssueEvent, isGitHubIssueCommentEvent, isGitHubPullRequestEvent, isGitHubPullRequestReviewCommentEvent, } from './types.js';
4
5
  import { ingestEvent as ingestGitHubEvent } from './ingest.js';
@@ -58,9 +59,10 @@ export async function handleGitHubEventsWebhook(request, store) {
58
59
  }
59
60
  }
60
61
  // GitHub signs no timestamp, so a captured delivery verifies forever; this window is what refuses its replay.
61
- const DELIVERY_REPLAY_TTL_MS = 24 * 60 * 60 * 1000;
62
+ const DELIVERY_REPLAY_TTL_MS = DAY_MS;
62
63
  const DELIVERY_REPLAY_MAX_IDS = 10_000;
63
- // SHORTCUT: seen ids live in this process and reset on restart or eviction; move them to the store if serve ever runs as several processes.
64
+ // A fast path only: the durable dedupe is the connector event log's content-derived idempotency key (ingest.ts, and the deletion key below),
65
+ // so a replay after a restart or an eviction is a stored 'duplicate' with no new rows.
64
66
  const seenDeliveries = new Map();
65
67
  /** Claims a delivery id for the window; false when it was claimed within the window already. The oldest claim goes first at the cap. */
66
68
  function claimDelivery(key, now) {
@@ -1,6 +1,7 @@
1
1
  // The write half every connector's ingest shares, after its own pre-check of the event log.
2
2
  import { remember } from '../api/index.js';
3
- import { requireGroup, storeFor } from '../store/index.js';
3
+ import { requireGroup } from '../store/index.js';
4
+ import { storeFor } from '../store/sqlite/store.js';
4
5
  import { RejectedValueError } from '../core/api-errors.js';
5
6
  /** Stores `opts` once per event key; null `opts` (an empty body) and a tombstoned value are logged with no memory, so a redelivery acks instead of retrying. */
6
7
  export async function rememberWithEventLog(ctx, event, opts) {
@@ -1,5 +1,6 @@
1
1
  import { archiveRaw } from '../../api/index.js';
2
- import { requireGroup, storeFor } from '../../store/index.js';
2
+ import { requireGroup } from '../../store/index.js';
3
+ import { storeFor } from '../../store/sqlite/store.js';
3
4
  /** Handles Slack `message_deleted`. The store logs the event in the archive's own transaction, so a crash cannot leave a retry
4
5
  * answering `not_found` where `duplicate` is due. */
5
6
  export async function handleMessageDeleted(ctx, input) {
@@ -1,4 +1,5 @@
1
- import { requireGroup, storeFor } from '../../store/index.js';
1
+ import { requireGroup } from '../../store/index.js';
2
+ import { storeFor } from '../../store/sqlite/store.js';
2
3
  import { rememberWithEventLog } from '../ingest.js';
3
4
  import { messageToRememberOpts } from './transform.js';
4
5
  /** Ingests a Slack message as a kind='raw' memory once per event id (Slack redelivers within a minute); the pre-check is only the fast path,
@@ -1,5 +1,6 @@
1
1
  import { envSlackAllowUnknownTeamFallback, envTenant } from '../../util/env.js';
2
- import { requireGroup, storeFor } from '../../store/index.js';
2
+ import { requireGroup } from '../../store/index.js';
3
+ import { storeFor } from '../../store/sqlite/store.js';
3
4
  /** Look up the tenant_id for a Slack team_id; the HIPPO_TENANT fallback applies only when slack_workspaces is empty (single-workspace install).
4
5
  * An unknown team on a multi-workspace install returns null (fail closed); SLACK_ALLOW_UNKNOWN_TEAM_FALLBACK=1 is the emergency escape. */
5
6
  export async function resolveTenantForSlackTeam(hippoRoot, teamId, store) {
@@ -65,7 +65,7 @@ function recalledTogether(a, b) {
65
65
  // A personal row pairs only inside its own scope; any other pair shows its id to someone else.
66
66
  if ((isPersonalScope(a.scope) || isPersonalScope(b.scope)) && a.scope !== b.scope)
67
67
  return false;
68
- if (a.tenantId !== b.tenantId)
68
+ if (a.tenant_id !== b.tenant_id)
69
69
  return false;
70
70
  const [x, y] = [a.origin_project ?? null, b.origin_project ?? null];
71
71
  return x === y || (x === '' && y !== null) || (y === '' && x !== null);
@@ -1,6 +1,6 @@
1
1
  import { createMemory, Layer } from '../core/memory.js';
2
2
  import { writeEntry, writeEntriesTogether } from '../store/entry-writes.js';
3
- import { loadAllDirtySummaries, loadChildrenOfSummary, applyRebuildResult, clearSummaryDirtyAfterBuild, } from '../store/summaries.js';
3
+ import { loadAllDirtySummaries, loadChildrenOfSummary, loadChildrenOfSummaries, childrenKey, applyRebuildResult, clearSummaryDirtyAfterBuild, } from '../store/summaries.js';
4
4
  import { RejectedValueError } from '../core/api-errors.js';
5
5
  import { generateDagSummary } from './dag-summary.js';
6
6
  import { overlapPartners } from './overlap-index.js';
@@ -50,7 +50,7 @@ export function clusterFacts(facts) {
50
50
  function partitionFactsByTenant(unparented) {
51
51
  const unparentedByTenant = new Map();
52
52
  for (const fact of unparented) {
53
- const key = derivationPartitionKey(fact.tenantId, fact.scope, fact.origin_project);
53
+ const key = derivationPartitionKey(fact.tenant_id, fact.scope, fact.origin_project);
54
54
  const bucket = unparentedByTenant.get(key);
55
55
  if (bucket)
56
56
  bucket.push(fact);
@@ -103,7 +103,7 @@ async function summarizeCluster(hippoRoot, cluster, home, opts, result) {
103
103
  result.factsLinked += writeEntriesTogether(hippoRoot, cluster.members.map((member) => ({ ...member, dag_parent_id: summaryEntry.id })));
104
104
  // Member writes just marked this fresh summary dirty; clear it, or the same sleep cycle's
105
105
  // rebuild pass would re-rebuild every new summary at twice the LLM cost.
106
- clearSummaryDirtyAfterBuild(hippoRoot, summaryEntry.id, summaryEntry.tenantId, 'buildDag');
106
+ clearSummaryDirtyAfterBuild(hippoRoot, summaryEntry.id, summaryEntry.tenant_id, 'buildDag');
107
107
  }
108
108
  export async function buildDag(hippoRoot, facts, opts) {
109
109
  const result = { candidateClusters: 0, summariesCreated: 0, factsLinked: 0, rejected: 0 };
@@ -111,7 +111,7 @@ export async function buildDag(hippoRoot, facts, opts) {
111
111
  const unparented = facts.filter((f) => f.dag_level === 1 && !f.dag_parent_id && f.tags.includes('extracted'));
112
112
  for (const [, tenantFacts] of partitionFactsByTenant(unparented)) {
113
113
  const home = {
114
- tenantId: tenantFacts[0].tenantId,
114
+ tenantId: tenantFacts[0].tenant_id,
115
115
  scope: derivationScope(tenantFacts[0].scope),
116
116
  originProject: tenantFacts[0].origin_project,
117
117
  baseHalfLifeDays,
@@ -126,8 +126,11 @@ export async function buildDag(hippoRoot, facts, opts) {
126
126
  return result;
127
127
  }
128
128
  /** Regenerate one dirty summary from its children and tally the outcome; throws reach the caller's isolation. */
129
- async function rebuildOneSummary(hippoRoot, summary, opts, result) {
130
- const children = loadChildrenOfSummary(hippoRoot, summary.id, summary.tenantId);
129
+ async function rebuildOneSummary(hippoRoot, summary, opts, result, preloaded, queuedIds) {
130
+ // A queued child may have been rebuilt earlier in this drain, so its preloaded content is stale: reread then.
131
+ const children = preloaded.some((c) => queuedIds.has(c.id))
132
+ ? loadChildrenOfSummary(hippoRoot, summary.id, summary.tenant_id)
133
+ : preloaded;
131
134
  if (children.length === 0) {
132
135
  clearZeroChildSummary(hippoRoot, summary, result);
133
136
  return;
@@ -196,6 +199,8 @@ export async function rebuildDirtySummaries(hippoRoot, opts) {
196
199
  failed: 0,
197
200
  capped,
198
201
  };
202
+ const childrenByParent = loadChildrenOfSummaries(hippoRoot, queue);
203
+ const queuedIds = new Set(queue.map((s) => s.id));
199
204
  for (const [index, summary] of queue.entries()) {
200
205
  // Yield the macrotask queue every 25 summaries: each iteration is synchronous SQLite, so a batch of up to 1000
201
206
  // would starve timers and IPC (server keep-alives, Vitest's birpc heartbeat).
@@ -203,13 +208,13 @@ export async function rebuildDirtySummaries(hippoRoot, opts) {
203
208
  await new Promise((resolve) => setImmediate(resolve));
204
209
  }
205
210
  try {
206
- await rebuildOneSummary(hippoRoot, summary, opts, result);
211
+ await rebuildOneSummary(hippoRoot, summary, opts, result, childrenByParent.get(childrenKey(summary.tenant_id, summary.id)) ?? [], queuedIds);
207
212
  }
208
213
  catch (err) {
209
214
  // One throw doesn't abort the queue; log enough to triage an exotic
210
215
  // failure (SQLite I/O, prepare), since audit() already catches its own.
211
216
  result.failed++;
212
- log.error(`rebuildDirtySummaries: summary ${summary.id} (tenant ${summary.tenantId}) failed: ${errorMessage(err)}`, errorFields(err));
217
+ log.error(`rebuildDirtySummaries: summary ${summary.id} (tenant ${summary.tenant_id}) failed: ${errorMessage(err)}`, errorFields(err));
213
218
  }
214
219
  }
215
220
  return result;
@@ -275,7 +280,7 @@ export async function buildEntityProfiles(hippoRoot, l2Summaries, opts) {
275
280
  const unparented = l2Summaries.filter((s) => s.dag_level === 2 && !s.dag_parent_id);
276
281
  for (const [, tenantL2s] of partitionFactsByTenant(unparented)) {
277
282
  const home = {
278
- tenantId: tenantL2s[0].tenantId,
283
+ tenantId: tenantL2s[0].tenant_id,
279
284
  scope: derivationScope(tenantL2s[0].scope),
280
285
  originProject: tenantL2s[0].origin_project,
281
286
  baseHalfLifeDays,
@@ -34,7 +34,7 @@ function entriesByPartition(entries) {
34
34
  const entriesByTenant = new Map();
35
35
  for (const entry of entries) {
36
36
  // EI2: also split by restricted scope, else a private copy can delete the readable one.
37
- const key = derivationPartitionKey(entry.tenantId, entry.scope, entry.origin_project);
37
+ const key = derivationPartitionKey(entry.tenant_id, entry.scope, entry.origin_project);
38
38
  const bucket = entriesByTenant.get(key);
39
39
  if (bucket)
40
40
  bucket.push(entry);
@@ -114,7 +114,7 @@ export function rankNonPinnedByTenant(entries, now, weights = MEMORY_VALUE_WEIGH
114
114
  continue; // pinned entries never compete for rescue (never condemned)
115
115
  // Default an undefined tenantId as dag.ts:341 does: a raw/legacy row can carry one at runtime,
116
116
  // and keying it "undefined" would split it into its own singleton tenant.
117
- const tenantId = e.tenantId ?? DEFAULT_TENANT_ID;
117
+ const tenantId = e.tenant_id ?? DEFAULT_TENANT_ID;
118
118
  const list = byTenant.get(tenantId);
119
119
  if (list)
120
120
  list.push(e);