hippo-memory 1.61.0 → 1.63.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 (377) hide show
  1. package/README.md +37 -53
  2. package/dist/agent-memories/apply.d.ts +3 -1
  3. package/dist/agent-memories/apply.js +23 -1
  4. package/dist/agent-memories/claude-code.d.ts +4 -1
  5. package/dist/agent-memories/claude-code.js +53 -9
  6. package/dist/agent-memories/report.d.ts +1 -0
  7. package/dist/agent-memories/report.js +2 -2
  8. package/dist/agent-memories/sync.d.ts +3 -3
  9. package/dist/agent-memories/sync.js +46 -24
  10. package/dist/agent-memories/types.d.ts +0 -2
  11. package/dist/ambient-store.d.ts +4 -4
  12. package/dist/ambient-store.js +4 -3
  13. package/dist/api/assemble.d.ts +7 -10
  14. package/dist/api/assemble.js +62 -66
  15. package/dist/api/audit.d.ts +2 -2
  16. package/dist/api/audit.js +2 -2
  17. package/dist/api/auth.d.ts +6 -6
  18. package/dist/api/auth.js +5 -7
  19. package/dist/api/context-select.d.ts +50 -0
  20. package/dist/api/context-select.js +342 -0
  21. package/dist/api/context-types.d.ts +12 -17
  22. package/dist/api/context.d.ts +5 -4
  23. package/dist/api/context.js +208 -535
  24. package/dist/api/dormant.js +1 -1
  25. package/dist/api/drill-down.d.ts +4 -4
  26. package/dist/api/drill-down.js +56 -41
  27. package/dist/api/outcome.d.ts +8 -13
  28. package/dist/api/outcome.js +13 -17
  29. package/dist/api/promote.d.ts +5 -8
  30. package/dist/api/promote.js +55 -66
  31. package/dist/api/quarantine.js +3 -2
  32. package/dist/api/recall-types.d.ts +43 -55
  33. package/dist/api/recall.d.ts +3 -3
  34. package/dist/api/recall.js +310 -456
  35. package/dist/api/remember.d.ts +2 -2
  36. package/dist/api/sleep.d.ts +7 -35
  37. package/dist/api/sleep.js +206 -220
  38. package/dist/api/tokens.d.ts +2 -2
  39. package/dist/api/tokens.js +2 -2
  40. package/dist/api/types.d.ts +6 -10
  41. package/dist/api/types.js +2 -4
  42. package/dist/audit-prune.d.ts +4 -6
  43. package/dist/audit-prune.js +3 -5
  44. package/dist/audit.js +11 -33
  45. package/dist/auth.d.ts +8 -9
  46. package/dist/auth.js +5 -7
  47. package/dist/autolearn.d.ts +1 -1
  48. package/dist/autolearn.js +1 -1
  49. package/dist/availability.js +3 -5
  50. package/dist/capture/command.d.ts +5 -4
  51. package/dist/capture/command.js +8 -22
  52. package/dist/capture/compact.d.ts +2 -2
  53. package/dist/capture/compact.js +11 -15
  54. package/dist/capture/extract.js +37 -115
  55. package/dist/capture-error.d.ts +1 -1
  56. package/dist/capture-error.js +1 -1
  57. package/dist/churn-git.d.ts +1 -1
  58. package/dist/churn-git.js +1 -1
  59. package/dist/cli/audit.js +3 -4
  60. package/dist/cli/auth.js +3 -6
  61. package/dist/cli/briefs.js +324 -306
  62. package/dist/cli/context.js +44 -34
  63. package/dist/cli/continuity.js +283 -271
  64. package/dist/cli/curate.d.ts +1 -1
  65. package/dist/cli/curate.js +43 -58
  66. package/dist/cli/dag.js +5 -9
  67. package/dist/cli/decisions.js +334 -345
  68. package/dist/cli/explain.js +68 -61
  69. package/dist/cli/goals.js +1 -1
  70. package/dist/cli/init.js +1 -1
  71. package/dist/cli/maintenance.js +62 -51
  72. package/dist/cli/playbooks.js +391 -379
  73. package/dist/cli/projects.js +11 -6
  74. package/dist/cli/recall.js +30 -44
  75. package/dist/cli/remember.js +118 -87
  76. package/dist/cli/session-hooks.js +106 -115
  77. package/dist/cli/setup.d.ts +1 -1
  78. package/dist/cli/setup.js +267 -250
  79. package/dist/cli/shared.js +3 -3
  80. package/dist/cli/slack.js +1 -1
  81. package/dist/cli/sleep.js +27 -1
  82. package/dist/cli/status.d.ts +4 -4
  83. package/dist/cli/status.js +80 -76
  84. package/dist/cli/transfer.js +88 -107
  85. package/dist/cli/usage.js +9 -6
  86. package/dist/cli.d.ts +1 -1
  87. package/dist/cli.js +4 -9
  88. package/dist/compaction-record.d.ts +2 -2
  89. package/dist/compaction-record.js +89 -68
  90. package/dist/compare.d.ts +11 -16
  91. package/dist/compare.js +11 -16
  92. package/dist/config.d.ts +19 -18
  93. package/dist/config.js +90 -67
  94. package/dist/connectors/github/backfill.d.ts +2 -2
  95. package/dist/connectors/github/backfill.js +8 -15
  96. package/dist/connectors/github/cli-impl.js +3 -8
  97. package/dist/connectors/github/deletion.d.ts +5 -12
  98. package/dist/connectors/github/deletion.js +5 -12
  99. package/dist/connectors/github/dlq.d.ts +6 -9
  100. package/dist/connectors/github/dlq.js +2 -3
  101. package/dist/connectors/github/ingest.d.ts +5 -7
  102. package/dist/connectors/github/ingest.js +8 -12
  103. package/dist/connectors/github/octokit-client.d.ts +3 -5
  104. package/dist/connectors/github/octokit-client.js +5 -6
  105. package/dist/connectors/github/signature.d.ts +9 -39
  106. package/dist/connectors/github/signature.js +9 -39
  107. package/dist/connectors/github/tenant-routing.d.ts +1 -1
  108. package/dist/connectors/github/tenant-routing.js +1 -1
  109. package/dist/connectors/github/transform.js +2 -2
  110. package/dist/connectors/github/types.d.ts +2 -10
  111. package/dist/connectors/github/types.js +1 -3
  112. package/dist/connectors/slack/deletion.d.ts +3 -8
  113. package/dist/connectors/slack/deletion.js +3 -8
  114. package/dist/connectors/slack/dlq.d.ts +1 -1
  115. package/dist/connectors/slack/ingest.d.ts +1 -1
  116. package/dist/connectors/slack/ingest.js +7 -16
  117. package/dist/connectors/slack/signature.d.ts +1 -1
  118. package/dist/connectors/slack/tenant-routing.d.ts +3 -5
  119. package/dist/connectors/slack/tenant-routing.js +3 -5
  120. package/dist/connectors/slack/transform.d.ts +5 -6
  121. package/dist/connectors/slack/transform.js +5 -6
  122. package/dist/connectors/slack/types.d.ts +2 -6
  123. package/dist/connectors/slack/types.js +1 -3
  124. package/dist/connectors/slack/web-client.js +10 -3
  125. package/dist/connectors/slack/workspaces.d.ts +3 -5
  126. package/dist/connectors/slack/workspaces.js +3 -5
  127. package/dist/consolidate/conflicts.js +3 -14
  128. package/dist/consolidate/decay.js +9 -29
  129. package/dist/consolidate/llm-passes.js +4 -5
  130. package/dist/consolidate/merge.js +8 -23
  131. package/dist/consolidate/run.d.ts +1 -8
  132. package/dist/consolidate/run.js +3 -25
  133. package/dist/consolidate/sleep.js +5 -17
  134. package/dist/consolidate/traces.js +9 -21
  135. package/dist/customer-notes.d.ts +5 -7
  136. package/dist/customer-notes.js +82 -76
  137. package/dist/dag.d.ts +10 -21
  138. package/dist/dag.js +189 -203
  139. package/dist/db/continuity.js +2 -2
  140. package/dist/db/migrations/v14.js +1 -1
  141. package/dist/db/migrations/v15.js +1 -2
  142. package/dist/db/migrations/v16.js +3 -4
  143. package/dist/db/migrations/v17.js +2 -3
  144. package/dist/db/migrations/v19.js +1 -1
  145. package/dist/db/migrations/v20.js +1 -1
  146. package/dist/db/migrations/v21.js +2 -6
  147. package/dist/db/migrations/v22.js +2 -4
  148. package/dist/db/migrations/v23.js +1 -1
  149. package/dist/db/migrations/v24.js +4 -6
  150. package/dist/db/migrations/v25.js +2 -3
  151. package/dist/db/migrations/v26.js +3 -3
  152. package/dist/db/migrations/v27.js +2 -10
  153. package/dist/db/migrations/v28.js +5 -8
  154. package/dist/db/migrations/v29.js +3 -4
  155. package/dist/db/migrations/v30.js +2 -2
  156. package/dist/db/migrations/v31.js +1 -1
  157. package/dist/db/migrations/v32.js +1 -1
  158. package/dist/db/migrations/v33.js +3 -3
  159. package/dist/db/migrations/v34.js +1 -1
  160. package/dist/db/migrations/v35.js +3 -4
  161. package/dist/db/migrations/v36.js +3 -4
  162. package/dist/db/migrations/v37.js +5 -5
  163. package/dist/db/migrations/v38.js +7 -8
  164. package/dist/db/migrations/v39.js +1 -1
  165. package/dist/db/migrations/v40.js +4 -16
  166. package/dist/db/migrations/v41.js +3 -4
  167. package/dist/db/migrations/v42.js +3 -4
  168. package/dist/db/migrations/v45.js +1 -1
  169. package/dist/db/migrations/v46.js +1 -1
  170. package/dist/db/migrations/v47.js +1 -1
  171. package/dist/db/migrations/v48.js +1 -1
  172. package/dist/decisions.d.ts +2 -2
  173. package/dist/decisions.js +97 -80
  174. package/dist/dedupe.js +86 -61
  175. package/dist/delivery-recorder.js +154 -135
  176. package/dist/doctor.js +129 -110
  177. package/dist/dormant.js +1 -4
  178. package/dist/embedding-provider.d.ts +4 -8
  179. package/dist/embedding-provider.js +4 -8
  180. package/dist/embeddings.js +55 -47
  181. package/dist/env.d.ts +1 -1
  182. package/dist/env.js +12 -12
  183. package/dist/escape.d.ts +5 -0
  184. package/dist/escape.js +10 -0
  185. package/dist/eval-stats.d.ts +1 -2
  186. package/dist/eval-stats.js +1 -2
  187. package/dist/eval-suite.js +27 -21
  188. package/dist/extract.js +4 -9
  189. package/dist/failure-log.d.ts +3 -3
  190. package/dist/failure-log.js +1 -1
  191. package/dist/forward-claim-detector.d.ts +2 -4
  192. package/dist/forward-claim-detector.js +6 -11
  193. package/dist/goals.d.ts +3 -3
  194. package/dist/goals.js +103 -91
  195. package/dist/graph/read.d.ts +2 -2
  196. package/dist/graph/read.js +5 -6
  197. package/dist/graph/types.d.ts +8 -8
  198. package/dist/graph/write.d.ts +7 -14
  199. package/dist/graph/write.js +16 -23
  200. package/dist/graph-extract.d.ts +7 -8
  201. package/dist/graph-extract.js +62 -72
  202. package/dist/graph-recall.d.ts +2 -2
  203. package/dist/graph-recall.js +55 -49
  204. package/dist/graph-stream.d.ts +5 -6
  205. package/dist/graph-stream.js +66 -57
  206. package/dist/graph-view.d.ts +2 -2
  207. package/dist/graph-view.js +7 -7
  208. package/dist/half-life-migration.d.ts +1 -2
  209. package/dist/half-life-migration.js +2 -3
  210. package/dist/hooks/codex-session.js +1 -1
  211. package/dist/hooks/codex-wrapper.d.ts +1 -1
  212. package/dist/hooks/codex-wrapper.js +3 -2
  213. package/dist/hooks/json-hooks.d.ts +2 -2
  214. package/dist/hooks/json-hooks.js +5 -4
  215. package/dist/hooks/opencode.d.ts +1 -1
  216. package/dist/hooks/opencode.js +5 -4
  217. package/dist/hooks/shared.d.ts +3 -7
  218. package/dist/hooks/shared.js +1 -8
  219. package/dist/http-util.d.ts +2 -3
  220. package/dist/http-util.js +3 -0
  221. package/dist/importers/core.d.ts +2 -9
  222. package/dist/importers/core.js +15 -30
  223. package/dist/importers/sources.js +2 -1
  224. package/dist/importers/vault.js +2 -20
  225. package/dist/incidents.d.ts +1 -1
  226. package/dist/incidents.js +46 -39
  227. package/dist/instruction-detect.d.ts +1 -1
  228. package/dist/instruction-detect.js +1 -1
  229. package/dist/invalidation.d.ts +3 -0
  230. package/dist/invalidation.js +160 -114
  231. package/dist/json.d.ts +5 -0
  232. package/dist/json.js +4 -0
  233. package/dist/judgment.js +1 -2
  234. package/dist/local-embedding.js +1 -1
  235. package/dist/mcp/admin-tools.js +7 -17
  236. package/dist/mcp/format.js +1 -1
  237. package/dist/mcp/framing.js +3 -6
  238. package/dist/mcp/protocol.d.ts +2 -5
  239. package/dist/mcp/protocol.js +1 -3
  240. package/dist/mcp/recall-tools.js +12 -15
  241. package/dist/mcp/request.js +4 -3
  242. package/dist/mcp/session-state.js +2 -3
  243. package/dist/mcp/stdio.js +2 -1
  244. package/dist/mcp/tools.js +9 -6
  245. package/dist/memory-value-weights.d.ts +5 -8
  246. package/dist/memory-value-weights.js +5 -8
  247. package/dist/memory-value.d.ts +13 -13
  248. package/dist/memory-value.js +26 -37
  249. package/dist/memory.d.ts +20 -22
  250. package/dist/memory.js +24 -48
  251. package/dist/multihop.d.ts +1 -1
  252. package/dist/multihop.js +3 -2
  253. package/dist/owner-validation.d.ts +4 -5
  254. package/dist/owner-validation.js +4 -5
  255. package/dist/physics.d.ts +4 -4
  256. package/dist/physics.js +7 -9
  257. package/dist/policies.d.ts +9 -10
  258. package/dist/policies.js +96 -81
  259. package/dist/postinstall.js +3 -6
  260. package/dist/predictions/planning-fallacy.d.ts +9 -14
  261. package/dist/predictions/planning-fallacy.js +10 -16
  262. package/dist/predictions/store.d.ts +15 -23
  263. package/dist/predictions/store.js +36 -33
  264. package/dist/processes.d.ts +2 -7
  265. package/dist/processes.js +88 -72
  266. package/dist/project-briefs.d.ts +2 -3
  267. package/dist/project-briefs.js +141 -118
  268. package/dist/project-identity.d.ts +22 -9
  269. package/dist/project-identity.js +47 -12
  270. package/dist/project-merge.d.ts +28 -5
  271. package/dist/project-merge.js +213 -46
  272. package/dist/project-remote.d.ts +12 -0
  273. package/dist/project-remote.js +138 -0
  274. package/dist/prompt-recall.js +1 -2
  275. package/dist/rate-limit.d.ts +1 -1
  276. package/dist/rate-limit.js +1 -1
  277. package/dist/raw-archive.d.ts +9 -0
  278. package/dist/raw-archive.js +70 -53
  279. package/dist/recall-history.d.ts +19 -20
  280. package/dist/recall-history.js +24 -42
  281. package/dist/recall-pipeline.js +4 -28
  282. package/dist/recall-scope.d.ts +7 -8
  283. package/dist/recall-scope.js +7 -8
  284. package/dist/recall-trace.d.ts +5 -9
  285. package/dist/recall-trace.js +6 -10
  286. package/dist/refine-llm.d.ts +1 -1
  287. package/dist/refine-llm.js +2 -2
  288. package/dist/reject-flow.d.ts +3 -4
  289. package/dist/reject-flow.js +122 -117
  290. package/dist/rejection.d.ts +5 -6
  291. package/dist/rejection.js +7 -15
  292. package/dist/rerankers/clef.d.ts +1 -1
  293. package/dist/rerankers/jev.d.ts +1 -2
  294. package/dist/rerankers/jev.js +4 -5
  295. package/dist/rerankers/llm.d.ts +1 -2
  296. package/dist/rerankers/llm.js +1 -2
  297. package/dist/rerankers/types.d.ts +1 -2
  298. package/dist/rrf.d.ts +2 -2
  299. package/dist/rrf.js +2 -2
  300. package/dist/search/bm25-search.d.ts +1 -1
  301. package/dist/search/bm25-search.js +2 -1
  302. package/dist/search/boosts.js +2 -1
  303. package/dist/search/hybrid.d.ts +1 -1
  304. package/dist/search/hybrid.js +2 -1
  305. package/dist/search/physics-search.d.ts +1 -1
  306. package/dist/search/physics-search.js +2 -1
  307. package/dist/search/types.d.ts +2 -0
  308. package/dist/search/types.js +3 -1
  309. package/dist/secret-detect.d.ts +4 -5
  310. package/dist/secret-detect.js +6 -10
  311. package/dist/server/auth.js +5 -5
  312. package/dist/server/client-ip.js +1 -1
  313. package/dist/server/cursor.js +2 -1
  314. package/dist/server/mcp-http.js +4 -4
  315. package/dist/server/request.d.ts +3 -6
  316. package/dist/server/request.js +6 -7
  317. package/dist/server/routes/admin.js +5 -4
  318. package/dist/server/routes/customer-notes.js +6 -5
  319. package/dist/server/routes/decisions.js +4 -3
  320. package/dist/server/routes/incidents.js +7 -5
  321. package/dist/server/routes/memories.js +7 -7
  322. package/dist/server/routes/policies.js +3 -2
  323. package/dist/server/routes/predictions.js +12 -15
  324. package/dist/server/routes/processes.js +3 -2
  325. package/dist/server/routes/project-briefs.js +8 -7
  326. package/dist/server/routes/recall.js +95 -93
  327. package/dist/server/routes/skills.js +6 -5
  328. package/dist/server/types.d.ts +1 -1
  329. package/dist/server/validation.d.ts +1 -2
  330. package/dist/server/validation.js +7 -14
  331. package/dist/server-detect.js +72 -58
  332. package/dist/server.d.ts +2 -2
  333. package/dist/server.js +131 -117
  334. package/dist/shared.d.ts +26 -17
  335. package/dist/shared.js +102 -104
  336. package/dist/skills.d.ts +3 -3
  337. package/dist/skills.js +88 -72
  338. package/dist/store/audit-event.d.ts +2 -2
  339. package/dist/store/audit-event.js +1 -1
  340. package/dist/store/candidates.d.ts +2 -2
  341. package/dist/store/candidates.js +4 -3
  342. package/dist/store/conflicts.js +30 -22
  343. package/dist/store/delete-and-batch.d.ts +11 -14
  344. package/dist/store/delete-and-batch.js +40 -91
  345. package/dist/store/entry-reads.d.ts +17 -25
  346. package/dist/store/entry-reads.js +59 -40
  347. package/dist/store/entry-row.d.ts +6 -24
  348. package/dist/store/entry-row.js +6 -24
  349. package/dist/store/entry-writes.d.ts +6 -7
  350. package/dist/store/entry-writes.js +13 -11
  351. package/dist/store/handoffs.d.ts +1 -1
  352. package/dist/store/handoffs.js +7 -10
  353. package/dist/store/index-and-stats.d.ts +2 -6
  354. package/dist/store/index-and-stats.js +4 -10
  355. package/dist/store/mirrors.d.ts +6 -19
  356. package/dist/store/mirrors.js +14 -39
  357. package/dist/store/open.js +9 -31
  358. package/dist/store/rows.d.ts +5 -11
  359. package/dist/store/rows.js +6 -11
  360. package/dist/store/search-rows.d.ts +17 -34
  361. package/dist/store/search-rows.js +34 -56
  362. package/dist/store/sessions.d.ts +4 -5
  363. package/dist/store/sessions.js +5 -6
  364. package/dist/store/summaries.d.ts +13 -17
  365. package/dist/store/summaries.js +26 -70
  366. package/dist/support-bundle.js +4 -8
  367. package/dist/tenant.d.ts +1 -5
  368. package/dist/token-ledger.d.ts +1 -1
  369. package/dist/token-ledger.js +3 -5
  370. package/dist/trace.js +1 -3
  371. package/dist/version.d.ts +1 -1
  372. package/dist/version.js +1 -1
  373. package/dist/working-memory.d.ts +1 -1
  374. package/extensions/openclaw-plugin/openclaw.plugin.json +1 -1
  375. package/extensions/openclaw-plugin/package.json +1 -1
  376. package/openclaw.plugin.json +1 -1
  377. package/package.json +1 -1
package/README.md CHANGED
@@ -22,7 +22,7 @@ npm install -g hippo-memory && hippo init
22
22
 
23
23
  Setting up every git repo under a folder in one go is a second step. The [Quick start](#quick-start) says what it changes, then gives the command.
24
24
 
25
- Package installation alone does not enable automatic preservation on every agent. Complete the documented setup and required host trust; capture and compaction coverage depend on the integration. See [automatic-save coverage](#does-installation-automatically-save-before-compaction).
25
+ Package installation alone does not enable automatic preservation on every agent. Complete the documented setup and required host trust; capture and compaction coverage depend on the integration.
26
26
 
27
27
  Having an AI agent install it? Point it at [llms-install.md](llms-install.md): it installs, wires hippo into the agents it finds, and verifies with `hippo doctor`.
28
28
 
@@ -544,9 +544,18 @@ database to `.hippo/backups/` first and logs every id it touched in the audit lo
544
544
  ```bash
545
545
  hippo projects --global # names, counts, live worktrees of this repo
546
546
  hippo projects merge hippo-wt-fix hippo --global # fold an old worktree name into its repo
547
- hippo projects repair --global # re-tag user-global merges by their parents
547
+ hippo projects repair --global # set aside note copies, fold old worktree names, re-tag merges
548
548
  ```
549
549
 
550
+ A project's name is the `id` in a committed `.hippo-project.json`, else its `origin` remote
551
+ (`github.com/acme/api`), else its folder name. So two repos both called `api` no longer share
552
+ memories in the global store. Rows saved under the old folder name stay visible to the
553
+ project, and `hippo projects repair` folds that name into the id unless two projects claim it.
554
+ `hippo sleep` runs that repair once per store after an upgrade, with a backup; in the global store
555
+ it leaves the name folds to you, since only `hippo projects repair --global` lists them for review.
556
+ Set `{"projectIdentity":{"remote":false}}` in the global `config.json` to keep folder names.
557
+ A long-running MCP or HTTP server reads a new project file or remote after a restart.
558
+
550
559
  **See what memory costs in tokens.** Every block of memory text hippo hands an agent (the
551
560
  per-prompt hook, the block `hippo compact-resume` restores after compaction, `hippo context`,
552
561
  `hippo recall`, the MCP tools, the HTTP API) is recorded in a token ledger: counts, surface
@@ -936,53 +945,36 @@ For how these mechanisms connect to LLM training, continual learning, and open r
936
945
 
937
946
  ## Comparison
938
947
 
939
- Two tables. The first is plain facts: where the data lives, what each tool needs and runs on, and what it has published. Several rows favour the other tools: a managed multi-user service, a native Python library, a full knowledge graph. The second lists design bets, choices a tool made rather than results it measured; what hippo has measured is under [Benchmarks](#benchmarks). Graph-first systems ([gbrain](https://hermesatlas.com/projects/garrytan/gbrain), [Zep](https://www.getzep.com/), [Cognee](https://www.cognee.ai/)), agent-managed systems ([Letta](https://github.com/letta-ai/letta-code)), and version-control or skill-distillation takes ([Memoria](https://github.com/matrixorigin/Memoria), [EverMind](https://evermind.ai/)) solve adjacent problems with different mechanics.
940
-
941
- ### What each tool is
942
-
943
- The rows from where the data lives to the graph were checked against each tool's own pages on 2026-09-28.
948
+ The AI-memory category matured fast in 2026. Hippo's specific take (bio-decay, strengthen-on-use, outcome-weighted half-lives) is one stance among several. The table below is a feature snapshot, not a verdict: graph-first systems ([gbrain](https://hermesatlas.com/projects/garrytan/gbrain), [Zep](https://www.getzep.com/), [Cognee](https://www.cognee.ai/)), agent-managed systems ([Letta](https://github.com/letta-ai/letta-code)), and version-control / skill-distillation takes ([Memoria](https://github.com/matrixorigin/Memoria), [EverMind](https://evermind.ai/)) all solve adjacent problems with different mechanics.
944
949
 
945
950
  | Feature | Hippo | [MemPalace](https://github.com/milla-jovovich/mempalace) | [Mem0](https://github.com/mem0ai/mem0) | [Basic Memory](https://github.com/basicmachines-co/basic-memory) | [gbrain](https://hermesatlas.com/projects/garrytan/gbrain) | [Zep](https://www.getzep.com/) | [Letta](https://github.com/letta-ai/letta-code) | [Cognee](https://www.cognee.ai/) | [Memoria](https://github.com/matrixorigin/Memoria) | [EverMind](https://evermind.ai/) |
946
951
  |---------|-------|-----------|------|-------------|--------|-----|-------|--------|---------|----------|
947
- | Where your data lives | Your machine or your server (SQLite) | Your machine (ChromaDB by default) | Where you run it, or Mem0's cloud | Your machine (Markdown files); cloud optional | Your machine (PGLite) or your Postgres | Zep's cloud (your own cloud on Enterprise) | Your machine; cloud backup with /login | Your machine by default; Cognee Cloud optional | Memoria Cloud, or self-hosted (Docker or embedded) | Your machine by default; EverOS Cloud optional |
948
- | Managed multi-user service | No (self-hosted, with tenants and API keys; hosted SaaS is planned for the commercial edition) | ? | Yes (hosted platform) | Yes (Teams) | ? (self-hosted server with OAuth) | Yes (Zep Cloud) | ? (cloud backup with /login) | Yes (Cognee Cloud) | ? (Memoria Cloud) | Yes (EverOS Cloud) |
949
- | Needs an account or model key | No | No (core path) | Yes (model key; account for the platform) | No (account for the cloud) | No (keyless mode) | Yes (Zep account; Graphiti needs a model key) | Yes (your own model keys) | No (local) | ? (account for Memoria Cloud) | Yes (an LLM key, OpenRouter) |
950
- | License | MIT | MIT | Apache-2.0 | AGPL-3.0 | MIT | Proprietary cloud (Graphiti: Apache-2.0) | Apache-2.0 | Apache-2.0 | Apache-2.0 | Apache-2.0 (EverOS) + cloud |
951
- | Runtime | Node.js 22.16+, no runtime deps | Python 3.9+ (ChromaDB by default) | Python or Node.js (server: Postgres + pgvector) | Python (SQLite by default) | Bun (PGLite or Postgres + pgvector) | Managed service (Graphiti: Python + a graph database) | Node.js (npm) | Python (graph and vector stores, or Postgres) | A CLI binary + a MatrixOne database | Python (SQLite + LanceDB) |
952
- | SDKs and APIs | CLI, HTTP API, Python SDK over HTTP | Python library, CLI | Python and Node.js libraries, a self-hosted server | CLI, cloud app | CLI, HTTP API | Python, TypeScript and Go SDKs | TypeScript SDK, CLI | Python and TypeScript SDKs, REST API, CLI | Python client, REST API, CLI | Python library, HTTP API, CLI |
953
- | Native Python library | No (the Python SDK calls hippo serve over HTTP) | Yes (mempalace) | Yes (mem0ai) | Yes (basic-memory) | No (installs with Bun) | Yes (Graphiti: graphiti-core) | ? (Letta Code ships on npm) | Yes (cognee) | ? (memoria-client) | Yes (everos) |
954
- | Graph or entity relations | Partial (entities from decisions and policies; recall --hops, off by default) | Yes (temporal entity graph) | Partial (entity linking; graph memory on Pro) | Yes (wikilinks and observations) | Yes (typed knowledge graph) | Yes (temporal knowledge graph) | No | Yes (knowledge graph) | No (typed claims) | ? (graph view in progress) |
955
- | Hybrid search (BM25 + embeddings) | Yes (BM25 by default; embeddings are an optional install) | Embeddings + spatial | Yes (semantic + BM25 + entity) | No | Yes (vec + rerank + graph) | Yes (graph + vec) | ? | Yes (GraphRAG) | Yes (vector + full-text) | Yes (mRAG, multi-modal) |
956
- | MCP server | Yes | Yes | Yes (hosted, needs an account) | Yes | Yes (stdio + HTTP/OAuth) | Yes (hosted, needs an account) | Yes (hosted, needs an API key) | Yes (first-party Claude/LangGraph) | Yes | ? |
957
- | Multi-agent shared memory | Yes | No | No | No | Yes (brain repo, team mounts) | Yes | Yes (shared memory blocks) | Yes | Yes (branch/merge across sessions) | Yes (multi-agent coordination) |
958
- | Auto-hook install | Yes (Claude Code hooks, OpenCode plugin) | No | No | No | No | No | No | No | No | No |
959
- | Cross-tool import (ChatGPT/Claude/Cursor) | Yes | No | No | No | Partial (data sources) | ? | No | Partial (28 data sources) | No (Git ops) | Partial (mRAG: PDFs/images/URLs) |
960
- | Git-friendly | Yes | No | No | Yes | Yes | No | Yes (memory tracked in git) | No | Yes (Git is the model) | ? |
961
- | Framework agnostic | Yes | Yes | Partial | Yes | Yes | Yes | Yes | Yes | Yes | Yes |
962
- | LongMemEval (published) | 85.6% R@5 from hippo recall, 96.8% with the budget lifted (4,000-token default budget; the benchmark scripts' best of five settings: 98.0% local / 99.8% voyage any-evidence, 88.5% local all-evidence; s_cleaned, per-haystack)\* | 96.6% raw / 100% reranked R@5 | 94.4 (hosted platform)\*\* | N/A | 95.53% all-evidence R@5 reranked, 93.19% without (s_cleaned\*) | 90.2% accuracy\*\* (LoCoMo 94.7%) | N/A | N/A | 88.78% overall accuracy w/ reader\*\* | 83.00% overall\*\* (LoCoMo 93.05%, HaluMem 93.04%) |
963
-
964
- \* Hippo's figures are on `longmemeval_s_cleaned` with a per-question haystack. On a default install, `hippo recall` puts an answer session in its top 5 for 85.6% of the 500 questions inside its default 4,000-token budget. With the budget lifted it scores 96.8%, but then it returns every candidate, so that figure measures the ranking ([result](docs/evals/2026-09-28-recall-cli-longmemeval-result.md)). The 98.0% and 99.8% are the benchmark scripts' retrieval, each the best of five settings, not `hippo recall`. Any-evidence R@5 counts a hit when any answer session is in the top 5, over all 500 questions: 98.0% with the free local MiniLM embedder (an optional install) and 99.8% with voyage-3-large (measured 2026-06-09, not re-run). All-evidence R@5 counts a hit only when every answer session is in the top 5, over the 470 questions that have an answer: 86.8 to 88.5% with MiniLM. gbrain first published 97.6%, an any-evidence score over all 500; its [report](https://github.com/garrytan/gbrain-evals/blob/main/docs/benchmarks/2026-05-07-longmemeval-s.md) now leads with all-evidence, 95.53% (449 of 470) with the paid Voyage rerank-2.5 reranker and 93.19% without it. On all-evidence recall gbrain is ahead. The June 2026 build scored 98.6 any-evidence; [`docs/evals/2026-09-23-longmemeval-reproduction.md`](docs/evals/2026-09-23-longmemeval-reproduction.md) has both runs. An older hippo number, 86.8% R@5 on `longmemeval_oracle` under pooled (non-per-haystack) retrieval, is not comparable to per-haystack figures.
965
-
966
- \*\* Different metric: these are end-to-end answer scores, not retrieval R@5. Mem0's 94.4 comes from its hosted platform, which its README says includes optimizations the open-source SDK lacks. Zep's 90.2% and 94.7% are accuracy figures from its homepage. Memoria's 88.78% and EverMind's 83% are overall accuracy with a reader LLM. Higher denominator + LLM helps. Not directly comparable to retrieval-only R@5 numbers above. The Mem0, Zep and Letta columns were last checked against each vendor's own pages on 2026-09-28.
967
-
968
- ### Design bets
969
-
970
- A Yes means the tool made that choice, not that the choice was shown to help. What hippo has measured about its own bets is under [Benchmarks](#benchmarks); decay, for one, tied with decay switched off. Spatial organization and lossless compression are MemPalace's bets.
971
-
972
- | Design bet | Hippo | [MemPalace](https://github.com/milla-jovovich/mempalace) | [Mem0](https://github.com/mem0ai/mem0) | [Basic Memory](https://github.com/basicmachines-co/basic-memory) | [gbrain](https://hermesatlas.com/projects/garrytan/gbrain) | [Zep](https://www.getzep.com/) | [Letta](https://github.com/letta-ai/letta-code) | [Cognee](https://www.cognee.ai/) | [Memoria](https://github.com/matrixorigin/Memoria) | [EverMind](https://evermind.ai/) |
973
- |---------|-------|-----------|------|-------------|--------|-----|-------|--------|---------|----------|
974
952
  | Decay by default | Yes | No | No | No | No | No | No | No | No | No |
975
953
  | Retrieval strengthening | Yes | No | No | No | No | No | No | Partial (recall tuning) | No | Partial (Skill Memory distills patterns) |
976
954
  | Reward-proportional decay | Yes | No | No | No | No | No | No | No | No | No |
977
- | Outcome tracking | Yes | No | No | No | No | No | No | No | No | Partial (Cases: agent trajectories) |
955
+ | Hybrid search (BM25 + embeddings) | Yes | Embeddings + spatial | Yes (semantic + BM25 + entity) | No | Yes (vec + rerank + graph) | Yes (graph + vec) | ? | Yes (GraphRAG) | Yes (vector + full-text) | Yes (mRAG, multi-modal) |
956
+ | Schema acceleration / knowledge graph | Yes (schema) | No | Partial (entity linking; graph memory on Pro) | No | Yes (typed KG, self-wiring) | Yes (temporal KG) | No | Yes (auto-ontologies) | No (typed claims) | Yes (hierarchical: user/group/agent) |
978
957
  | Conflict detection + resolution | Yes | No | Partial (hosted platform marks superseded facts) | No | Yes (eval-surfaced) | Yes (auto-invalidate stale facts) | No | No | Yes (auto-detect + quarantine) | Partial (temporal tracking) |
958
+ | Multi-agent shared memory | Yes | No | No | No | Yes (brain repo, team mounts) | Yes | Yes (shared memory blocks) | Yes | Yes (branch/merge across sessions) | Yes (multi-agent coordination) |
979
959
  | Transfer scoring | Yes | No | No | No | No | No | No | No | No | No |
960
+ | Outcome tracking | Yes | No | No | No | No | No | No | No | No | Partial (Cases: agent trajectories) |
980
961
  | Confidence tiers | Yes | No | No | No | No (typed facts) | No | No | No | No | No |
981
- | Schema acceleration | Yes | No | No | No | No | No | No | No | No | No |
982
962
  | Spatial organization | No | Yes (wings/halls/rooms) | No | No | No | No | No | No | No | No |
983
963
  | Lossless compression | No | Yes (AAAK, 30x) | No | No | No | No | No | No | No | No |
964
+ | Cross-tool import (ChatGPT/Claude/Cursor) | Yes | No | No | No | Partial (data sources) | ? | No | Partial (28 data sources) | No (Git ops) | Partial (mRAG: PDFs/images/URLs) |
965
+ | Auto-hook install | Yes | No | No | No | No | No | No | No | No | No |
966
+ | MCP server | Yes | Yes | Yes (hosted, needs an account) | Yes | Yes (stdio + HTTP/OAuth) | Yes (hosted, needs an account) | Yes (hosted, needs an API key) | Yes (first-party Claude/LangGraph) | Yes | ? |
967
+ | Zero runtime deps | Yes | No (ChromaDB) | No | No | No (PGLite or PG+pgvector) | No (managed service) | No (npm deps) | No (Python deps) | Yes (single Rust binary) | No (managed + OSS) |
968
+ | LongMemEval (best published) | 98.0% local / 99.8% voyage any-evidence R@5; 88.5% local all-evidence R@5 (s_cleaned, per-haystack)\* | 96.6% raw / 100% reranked R@5 | 94.4 (hosted platform)\*\* | N/A | 95.53% all-evidence R@5 reranked, 93.19% without (s_cleaned\*) | 90.2% accuracy\*\* (LoCoMo 94.7%) | N/A | N/A | 88.78% overall accuracy w/ reader\*\* | 83.00% overall\*\* (LoCoMo 93.05%, HaluMem 93.04%) |
969
+ | Git-friendly | Yes | No | No | Yes | Yes | No | Yes (memory tracked in git) | No | Yes (Git is the model) | ? |
970
+ | Framework agnostic | Yes | Yes | Partial | Yes | Yes | Yes | Yes | Yes | Yes | Yes |
971
+ | License | MIT | (open) | Apache-2.0 | (open) | MIT | Proprietary cloud (Graphiti: Apache-2.0) | Apache-2.0 | MIT (core) | Apache-2.0 | Apache-2.0 (OSS) + cloud |
984
972
 
985
- Different tools answer different questions. Mem0 and Basic Memory implement "save everything, search later." MemPalace implements "store everything, organize spatially for retrieval." gbrain, Zep, and Cognee implement "extract typed entities and relationships into a knowledge graph." Letta implements "the agent edits its own memory blocks." Memoria implements "Git-style version control over the memory state itself." EverMind implements "self-evolving Skill Memory + multi-modal retrieval over hierarchical scopes." Hippo implements "learn what is wrong and rank it down." These are complementary takes, not a single-axis ranking: memory lifecycle (Hippo) + GraphRAG (gbrain/Cognee/Zep) + agent-self-edit (Letta) + memory-VCS (Memoria) + skill-distillation (EverMind) cover different parts of the same problem.
973
+ \* Hippo's figures are on `longmemeval_s_cleaned` with a per-question haystack, each the best of five retrieval settings in the benchmark scripts, not `hippo recall`. Any-evidence R@5 counts a hit when any answer session is in the top 5, over all 500 questions: 98.0% with the free local MiniLM embedder (an optional install) and 99.8% with voyage-3-large (measured 2026-06-09, not re-run). All-evidence R@5 counts a hit only when every answer session is in the top 5, over the 470 questions that have an answer: 86.8 to 88.5% with MiniLM. gbrain first published 97.6%, an any-evidence score over all 500; its [report](https://github.com/garrytan/gbrain-evals/blob/main/docs/benchmarks/2026-05-07-longmemeval-s.md) now leads with all-evidence, 95.53% (449 of 470) with the paid Voyage rerank-2.5 reranker and 93.19% without it. On all-evidence recall gbrain is ahead. The June 2026 build scored 98.6 any-evidence; [`docs/evals/2026-09-23-longmemeval-reproduction.md`](docs/evals/2026-09-23-longmemeval-reproduction.md) has both runs. An older hippo number, 86.8% R@5 on `longmemeval_oracle` under pooled (non-per-haystack) retrieval, is not comparable to per-haystack figures.
974
+
975
+ \*\* Different metric: these are end-to-end answer scores, not retrieval R@5. Mem0's 94.4 comes from its hosted platform, which its README says includes optimizations the open-source SDK lacks. Zep's 90.2% and 94.7% are accuracy figures from its homepage. Memoria's 88.78% and EverMind's 83% are overall accuracy with a reader LLM. Higher denominator + LLM helps. Not directly comparable to retrieval-only R@5 numbers above. The Mem0, Zep and Letta columns were last checked against each vendor's own pages on 2026-09-28.
976
+
977
+ Different tools answer different questions. Mem0 and Basic Memory implement "save everything, search later." MemPalace implements "store everything, organize spatially for retrieval." gbrain, Zep, and Cognee implement "extract typed entities and relationships into a knowledge graph." Letta implements "the agent edits its own memory blocks." Memoria implements "Git-style version control over the memory state itself." EverMind implements "self-evolving Skill Memory + multi-modal retrieval over hierarchical scopes." Hippo implements "learn what is wrong and stop repeating it." These are complementary takes, not a single-axis ranking: bio-lifecycle (Hippo) + GraphRAG (gbrain/Cognee/Zep) + agent-self-edit (Letta) + memory-VCS (Memoria) + skill-distillation (EverMind) cover different parts of the same problem.
986
978
 
987
979
  ---
988
980
 
@@ -1090,7 +1082,7 @@ node run.mjs --adapter all
1090
1082
 
1091
1083
  ### How do I give Claude Code memory between sessions?
1092
1084
 
1093
- Run `npm install -g hippo-memory`, then `hippo init` in the project. If the project has a `CLAUDE.md`, init adds a short block telling Claude to run `hippo context --auto` when a session starts. It also adds 7 hook entries to Claude Code's settings that keep your pinned memories plus up to 5 that match the prompt in context, save a task snapshot and the memories a compaction summary lists, store failed tool calls as lessons, and run `hippo sleep` when the session ends, and it sets up a daily 6:15am run. [What hippo init changes](#what-hippo-init-changes) lists everything. The [Claude Code plugin](https://github.com/kitfunso/hippo-memory/tree/master/extensions/claude-code-plugin) is the alternative to these hooks; use one, not both. To set up every git repo up to three folders below your home directory at once, know what the scan changes first: each repo gets its own store, seeded from a year of its git history, the same hooks go in when one of those repos uses Claude Code, and the daily run is set up, but no block goes into any repo's `CLAUDE.md`. The command is `hippo init --scan ~`; run `hippo init` in the projects where you want the block.
1085
+ Run `npm install -g hippo-memory`, then `hippo init` in the project. If the project has a `CLAUDE.md`, init adds a short block telling Claude to run `hippo context --auto` when a session starts. It also adds hooks to Claude Code's settings that keep your pinned memories in context, save a task snapshot before compaction, and run `hippo sleep` when the session ends. `hippo init --scan ~` gives every git repo under your home folder a store and installs the same hooks, but adds no block to any `CLAUDE.md`. The [Claude Code plugin](https://github.com/kitfunso/hippo-memory/tree/master/extensions/claude-code-plugin) is the alternative to these hooks; use one, not both.
1094
1086
 
1095
1087
  ### How do I give Cursor memory between sessions?
1096
1088
 
@@ -1098,19 +1090,11 @@ Run `npm install -g hippo-memory`, then `hippo init` in the project. If the proj
1098
1090
 
1099
1091
  ### How do I give Codex memory across sessions?
1100
1092
 
1101
- `hippo init` adds its instructions to your `AGENTS.md`, which Codex reads before it starts work. When Codex is installed, init also adds two hooks to Codex's `hooks.json`: one puts your pinned memories plus up to 5 that match the prompt into every prompt, the other makes the next prompt send them again after a compaction. Codex asks you to trust each new hook once in `/hooks`, and skips it until you do. Capturing Codex sessions is opt-in: `hippo hook install codex` wraps the Codex launcher, and `hippo hook uninstall codex` removes the wrapper and the hooks.
1093
+ `hippo init` adds its instructions to your `AGENTS.md`, which Codex reads before it starts work. Capturing Codex sessions is opt-in: `hippo hook install codex` wraps the Codex launcher, and `hippo hook uninstall codex` removes the wrapper.
1102
1094
 
1103
1095
  ### Which agents does hippo work with?
1104
1096
 
1105
- `hippo init` detects Claude Code, Codex, Cursor, OpenClaw, OpenCode and Pi. It installs hooks for Claude Code and OpenCode, adds 2 hooks to Codex's `hooks.json` when Codex is installed (Codex runs them once you trust them in `/hooks`), and adds instructions to an existing `AGENTS.md` for Codex, Cursor, OpenClaw and Pi. It only patches instruction files that already exist. Any MCP client can use the [MCP server](#mcp-server), and other tools can call the CLI or the HTTP API that `hippo serve` starts.
1106
-
1107
- ### Does installation automatically save before compaction?
1108
-
1109
- Package installation alone does not enable automatic preservation on every agent. Complete the documented setup and required host trust; capture and compaction coverage depend on the integration.
1110
-
1111
- With the Claude Code hooks or native plugin configured, PreCompact saves a derivable working-state snapshot and a compaction record; PostCompact extracts the lessons listed in the compaction summary. A snapshot, a compaction record and a useful memory are different outputs, and post-compaction extraction is not a guarantee that every earlier lesson was saved before loss. Hippo's Codex hooks currently provide prompt delivery and post-compaction re-injection, with one-time trust in `/hooks`; they install no PreCompact save hook. Codex session-end capture requires the opt-in wrapper. MCP tool access and instruction files alone do not provide automatic lifecycle capture.
1112
-
1113
- Follow the [integration recipes](https://github.com/kitfunso/hippo-memory/tree/master/integrations) for the exact agent and mode. Automatic pre-loss preservation across all supported agents is planned under AZ4-AZ6 in the [roadmap](https://github.com/kitfunso/hippo-memory/blob/master/ROADMAP.md#current-execution-index); it is not a universal install-only capability today.
1097
+ `hippo init` detects Claude Code, Codex, Cursor, OpenClaw, OpenCode and Pi, and wires itself into each one's instruction file, hooks or plugin. It only patches instruction files that already exist. Any MCP client can use the [MCP server](#mcp-server), and other tools can call the CLI or the HTTP API that `hippo serve` starts.
1114
1098
 
1115
1099
  ### Can I use hippo as an MCP memory server?
1116
1100
 
@@ -1118,7 +1102,7 @@ Yes. `hippo mcp` runs the server over stdio, and `npx -y hippo-memory mcp` runs
1118
1102
 
1119
1103
  ### How is hippo different from mem0?
1120
1104
 
1121
- mem0 uses a language model to extract memories, OpenAI by default in its open-source library, and memories stored through its hosted MCP server live in your Mem0 account ([mem0 docs](https://docs.mem0.ai/platform/mem0-mcp), checked 2026-09-28). Hippo stores memories in SQLite on your machine, needs no account and no model, and `hippo init` wires it into the coding agents it finds. mem0's platform marks an older fact superseded when a newer one replaces it; in hippo you run `hippo supersede` yourself. Hippo also lets you mark a recalled memory wrong with `hippo outcome --bad`, and it drops out of the top results.
1105
+ mem0 uses a language model to extract memories, OpenAI by default in its open-source library, and memories stored through its hosted MCP server live in your Mem0 account ([mem0 docs](https://docs.mem0.ai/platform/mem0-mcp), checked 2026-09-28). Hippo stores memories in SQLite on your machine, needs no account and no model, and `hippo init` wires it into the coding agents it finds. mem0's platform and hippo both mark an older fact superseded when a newer one replaces it. Hippo also lets you mark a recalled memory wrong with `hippo outcome --bad`, and it drops out of the top results.
1122
1106
 
1123
1107
  ### Is this just RAG?
1124
1108
 
@@ -1126,7 +1110,7 @@ No. RAG searches a fixed corpus. Hippo's store changes as your agent works: a me
1126
1110
 
1127
1111
  ### Does it need embeddings?
1128
1112
 
1129
- No. Recall runs on BM25 out of the box, with no model and no network call, and a default install has no embedder. Embeddings are an optional install for hybrid search. On LongMemEval-S, where each question gets its own haystack, `hippo recall` on a default install puts an answer session in its top five for 85.6% of questions inside its 4,000-token budget, and 87.6% with the free local MiniLM embedder; the budget, not the embedder, is most of the gap to the benchmark scripts. Those scripts, which are not `hippo recall`, fuse BM25 with MiniLM and reach 98.0% recall@5 at their best of five settings, counting a hit when any answer session is in the top five. On LongMemEval's oracle split with one pooled store, BM25 alone scored 74.0% recall@5 in v0.11. These runs use different setups, so they are not a before and after.
1113
+ No. Recall runs on BM25 out of the box, with no model and no network call, and a default install has no embedder. Embeddings are an optional install for hybrid search. On LongMemEval-S, where each question gets its own haystack, the benchmark scripts (not `hippo recall`) fuse BM25 with the free local MiniLM embedder and reach 98.0% recall@5, counting a hit when any answer session is in the top five. On LongMemEval's oracle split with one pooled store, BM25 alone scored 74.0% recall@5 in v0.11. The two runs use different setups, so they are not a before and after.
1130
1114
 
1131
1115
  ### Do I still need CLAUDE.md?
1132
1116
 
@@ -1138,11 +1122,11 @@ Mark it, and it drops out of the top results. `hippo outcome --bad` weakens the
1138
1122
 
1139
1123
  ### Where does hippo keep my data?
1140
1124
 
1141
- On your machine, in SQLite: `.hippo/hippo.db` in each project, plus a global store in `~/.hippo/` for lessons shared across projects, with markdown mirrors you can read and commit. Recall makes no network call by default. Text goes to an outside provider only through features that use one: an API embedder, the Jev, CLEF or LLM reranker, `hippo refine`, and the fact extraction `hippo sleep` runs through Anthropic's API whenever `ANTHROPIC_API_KEY` is set in its environment. To turn that last one off, set `{"extraction":{"enabled":false}}` in `.hippo/config.json`.
1125
+ On your machine, in SQLite: `.hippo/hippo.db` in each project, plus a global store in `~/.hippo/` for lessons shared across projects, with markdown mirrors you can read and commit. Recall makes no network call by default. Text goes to an outside provider only through features that use one: an API embedder, the Jev or LLM reranker, `hippo refine`, and the fact extraction `hippo sleep` runs through Anthropic's API whenever `ANTHROPIC_API_KEY` is set in its environment. To turn that last one off, set `{"extraction":{"enabled":false}}` in `.hippo/config.json`.
1142
1126
 
1143
1127
  ### What does hippo cost?
1144
1128
 
1145
- Nothing. Hippo is MIT-licensed and needs no account or API key. Optional features that call an outside provider bill through it: the Jev reranker costs about 0.0004 USD a recall, and API embedders and sleep's fact extraction bill your own keys. Memory text handed to your agent uses context tokens when it is sent, and again, usually at the cheaper cached-input rate, on each later model call until the host compacts. `hippo tokens` shows both for the hook and compact-resume blocks, and the sent tokens for the rest.
1129
+ Nothing. Hippo is MIT-licensed and needs no account or API key. Optional features that call an outside provider bill through it: the Jev reranker costs about 0.0004 USD a recall, and API embedders and sleep's fact extraction bill your own keys. Memory text handed to your agent uses context tokens, and `hippo tokens` shows how many.
1146
1130
 
1147
1131
  ### Is it production-ready?
1148
1132
 
@@ -1150,7 +1134,7 @@ Judge it by what is tested. 3,500+ tests run against a real database, with no mo
1150
1134
 
1151
1135
  ### Has hippo been shown to make agents better at their work?
1152
1136
 
1153
- No. The published numbers measure retrieval: whether the right memory comes back, and whether a memory marked wrong stays out of the results. Decay and sleep are design choices, not measured wins. In the [mechanism audit](https://github.com/kitfunso/hippo-memory/blob/master/docs/evals/2026-09-23-mechanism-audit-round2-result.md), decay had no measurable effect against decay switched off (-0.7 points [-1.4, 0.1] on a 20-session synthetic test, too short for a 365-day half-life to act), and sleep lowered LongMemEval hit@5 by 3.6 points [-5.8, -1.4] under the audit's declared scorer; no scorer there showed sleep helping recall. Every measurement, including failed runs and one retracted claim, is indexed in [docs/evals](https://github.com/kitfunso/hippo-memory/blob/master/docs/evals/README.md).
1137
+ Not yet. The published numbers measure retrieval: whether the right memory comes back, and whether a memory marked wrong stays out of the results. A paired test that runs real agent sessions with and without hippo is under way. Every measurement, including failed runs and one retracted claim, is indexed in [docs/evals](https://github.com/kitfunso/hippo-memory/blob/master/docs/evals/README.md).
1154
1138
 
1155
1139
  ---
1156
1140
 
@@ -24,6 +24,8 @@ export interface ContainerWork {
24
24
  readonly adopt: ReadonlyMap<string, readonly MemoryEntry[]>;
25
25
  /** Legacy rows a new row of the key supersedes, by key (second round). */
26
26
  readonly replace: ReadonlyMap<string, readonly MemoryEntry[]>;
27
+ /** This container's prefixes under the project's earlier names; their rows move here, so a new id imports nothing twice. */
28
+ readonly legacyPrefixes: readonly string[];
27
29
  }
28
30
  export interface ContainerOutcome {
29
31
  readonly tally: Tally;
@@ -34,7 +36,7 @@ export interface ContainerOutcome {
34
36
  }
35
37
  /** Throws SQLITE_BUSY when another writer holds the store past its busy timeout; nothing is written then. */
36
38
  export declare function syncContainer(s: StoreSession, work: ContainerWork): ContainerOutcome;
37
- export type SetAsideWhy = 'note-gone' | 'note-changed' | 'handover' | 'project-merge';
39
+ export type SetAsideWhy = 'note-gone' | 'note-changed' | 'handover' | 'project-merge' | 'project-repair';
38
40
  export type SetAsideResult = {
39
41
  readonly kind: 'untagged';
40
42
  readonly entry: MemoryEntry;
@@ -1,6 +1,6 @@
1
1
  // One container's sync in one transaction on the caller's handle: lookup, plan, then every write (plan designs 6 to 8).
2
2
  import { appendAuditEvent } from '../audit.js';
3
- import { deleteDormantRow, dormantSnapshotsBySourcePrefix, insertDormantRow, readDormantSnapshot } from '../dormant.js';
3
+ import { deleteDormantRow, dormantSnapshotsBySourcePrefix, insertDormantRow, readDormantSnapshot, replaceDormantEntry } from '../dormant.js';
4
4
  import { gatedWrite } from '../gated-write.js';
5
5
  import { Layer, calculateStrength, createMemory } from '../memory.js';
6
6
  import { findRejectedValue, rejectionDigest } from '../rejection.js';
@@ -65,6 +65,8 @@ class ContainerRun {
65
65
  }
66
66
  run() {
67
67
  this.adoptLegacy();
68
+ for (const old of this.w.legacyPrefixes)
69
+ this.adoptPrefix(old);
68
70
  const live = selectLiveEntriesBySourcePrefix(this.s.db, this.s.tenantId, this.w.prefix);
69
71
  for (const row of [...live, ...[...this.w.replace.values()].flat()])
70
72
  this.rows.set(row.id, row);
@@ -116,6 +118,26 @@ class ContainerRun {
116
118
  }
117
119
  }
118
120
  }
121
+ /** Rows filed under an earlier project name keep their id and history; dormant ones move too, scanned only until this prefix holds live rows. */
122
+ adoptPrefix(old) {
123
+ const origin = this.s.originProject ?? null;
124
+ let moved = 0;
125
+ for (const row of selectLiveEntriesBySourcePrefix(this.s.db, this.s.tenantId, old)) {
126
+ const source = this.w.prefix + row.source.slice(old.length);
127
+ const done = this.s.db.prepare(`UPDATE memories SET source = ?, origin_project = COALESCE(?, origin_project) WHERE id = ? AND tenant_id = ? AND source = ?`).run(source, origin, row.id, row.tenantId, row.source);
128
+ if (Number(done.changes ?? 0) === 0)
129
+ continue;
130
+ moved++;
131
+ this.mirror.push({ ...row, source, origin_project: origin ?? row.origin_project });
132
+ }
133
+ this.tally.renamed += moved;
134
+ if (moved === 0 && (this.w.container.items.length === 0 || selectLiveEntriesBySourcePrefix(this.s.db, this.s.tenantId, this.w.prefix).length > 0))
135
+ return;
136
+ for (const snap of dormantSnapshotsBySourcePrefix(this.s.db, this.s.tenantId, old)) {
137
+ const source = this.w.prefix + snap.entry.source.slice(old.length);
138
+ replaceDormantEntry(this.s.db, this.s.tenantId, snap.entry.id, { ...snap.entry, source, origin_project: origin ?? snap.entry.origin_project });
139
+ }
140
+ }
119
141
  refusals() {
120
142
  const out = new Map();
121
143
  for (const item of this.w.container.items) {
@@ -1,3 +1,4 @@
1
+ import { type ProjectIdentity } from '../project-identity.js';
1
2
  import type { Adapter, AdapterContext, Listing } from './types.js';
2
3
  /** Claude Code's auto memory folder names for a project: its checkout, which subfolders share, or the folder itself outside a repository. */
3
4
  export declare function claudeMemoryFolderNames(projectRoot: string, platform: NodeJS.Platform): Set<string>;
@@ -6,6 +7,8 @@ export declare function claudeCheckoutRoot(top: string, gitDir: string, common:
6
7
  /** Claude Code's folder name for a path: non-alphanumerics made '-', and a name over 200 characters cut to 200 plus a base-36 hash of the whole path. */
7
8
  export declare function claudeFolderName(root: string): string;
8
9
  export declare const claudeCodeAdapter: Adapter;
9
- /** Post-compact's read: the session's own notes folder and nothing else, so no git call runs inside the hook's time limit. */
10
+ /** A session's own notes folder and nothing else, with no git call, so post-compact can read it inside the hook's time limit. */
10
11
  export declare function claudeTranscriptListing(ctx: AdapterContext, transcriptPath: string): Listing;
12
+ /** The project a session folder's notes belong to: the one Claude named the folder for, the session's start folder, else cwd or a parent; null when none matches. */
13
+ export declare function transcriptNotesProject(transcriptPath: string, cwd: string | null, machine: Pick<AdapterContext, 'platform' | 'env'>): ProjectIdentity | null;
11
14
  //# sourceMappingURL=claude-code.d.ts.map
@@ -1,7 +1,7 @@
1
1
  // Claude Code's auto memory: frontmatter `.md` notes in a per-project folder, plus the `autoMemoryDirectory` user folder.
2
2
  import fs from 'node:fs';
3
3
  import path from 'node:path';
4
- import { realpathOrResolve } from '../project-identity.js';
4
+ import { realpathOrResolve, resolveProjectIdentity } from '../project-identity.js';
5
5
  import { isStringValue } from '../capture-contract.js';
6
6
  import { isJsonObject } from '../hooks/shared.js';
7
7
  import { expandHome, frontmatterField, itemTime, readTextFile, splitFrontmatter } from './files.js';
@@ -45,23 +45,67 @@ export const claudeCodeAdapter = {
45
45
  return { tool: 'claude-code', home: config, containers: readFolders(folders, scope, ctx.platform), warnings };
46
46
  },
47
47
  };
48
- /** Post-compact's read: the session's own notes folder and nothing else, so no git call runs inside the hook's time limit. */
48
+ /** A session's own notes folder and nothing else, with no git call, so post-compact can read it inside the hook's time limit. */
49
49
  export function claudeTranscriptListing(ctx, transcriptPath) {
50
50
  const config = ctx.env.CLAUDE_CONFIG_DIR || path.join(ctx.home, '.claude');
51
51
  const folder = path.join(path.dirname(transcriptPath), 'memory');
52
52
  return { tool: 'claude-code', home: config, containers: readFolders([folder], 'project', ctx.platform), warnings: [] };
53
53
  }
54
+ /** The project a session folder's notes belong to: the one Claude named the folder for, the session's start folder, else cwd or a parent; null when none matches. */
55
+ export function transcriptNotesProject(transcriptPath, cwd, machine) {
56
+ const fold = (name) => (machine.platform === 'win32' ? name.toLowerCase() : name);
57
+ const folder = fold(path.basename(path.dirname(transcriptPath)));
58
+ const start = transcriptStartCwd(transcriptPath);
59
+ const pinned = pinnedProjectDirName(machine.env);
60
+ if (pinned !== null && fold(pinned) === folder) {
61
+ // A pinned name stands for whatever project the session ran in, so its start folder decides.
62
+ const from = start ?? cwd;
63
+ return from !== null && fs.existsSync(from) ? resolveProjectIdentity(from) : null;
64
+ }
65
+ for (const from of [start, cwd]) {
66
+ for (let dir = from === null ? null : path.resolve(from); dir !== null; dir = path.dirname(dir) === dir ? null : path.dirname(dir)) {
67
+ // A folder gone from disk resolves to its bare name, never the project it was in, so it decides nothing.
68
+ if ([dir, realpathOrResolve(dir)].some((d) => fold(claudeFolderName(d)) === folder))
69
+ return fs.existsSync(dir) ? resolveProjectIdentity(dir) : null;
70
+ }
71
+ }
72
+ return null;
73
+ }
74
+ // Claude writes the cwd on each message line after a few header lines; 64 KB holds the first with room to spare.
75
+ const START_SCAN_BYTES = 64 * 1024;
76
+ /** The cwd on the transcript's first line that has one: the folder Claude named the session folder for, which a folder name alone cannot give back. */
77
+ function transcriptStartCwd(transcriptPath) {
78
+ if (!fs.existsSync(transcriptPath))
79
+ return null;
80
+ const fd = fs.openSync(transcriptPath, 'r');
81
+ try {
82
+ const buf = Buffer.alloc(START_SCAN_BYTES);
83
+ const lines = buf.subarray(0, fs.readSync(fd, buf, 0, buf.length, 0)).toString('utf8').split('\n');
84
+ for (const line of lines) {
85
+ const cwd = /"cwd":"((?:[^"\\]|\\.)*)"/.exec(line)?.[1];
86
+ if (cwd === undefined)
87
+ continue;
88
+ // SAFETY: the match is the body of one JSON string, so parsing it in quotes yields a string.
89
+ return JSON.parse(`"${cwd}"`);
90
+ }
91
+ return null;
92
+ }
93
+ finally {
94
+ fs.closeSync(fd);
95
+ }
96
+ }
54
97
  function projectFolders(ctx, config) {
55
98
  const projects = path.join(config, 'projects');
56
99
  const names = ctx.projectRoot === undefined ? [] : [...claudeMemoryFolderNames(ctx.projectRoot, ctx.platform)];
57
- const pinned = ctx.env.CLAUDE_CODE_PROJECT_DIR_NAME;
58
- // Claude reads the pinned name only alongside a pinned config folder.
59
- if (ctx.env.CLAUDE_CONFIG_DIR && pinned !== undefined && PROJECT_DIR_NAME.test(pinned))
100
+ const pinned = pinnedProjectDirName(ctx.env);
101
+ if (pinned !== null)
60
102
  names.push(pinned);
61
- const folders = names.map((name) => path.join(projects, name, 'memory'));
62
- if (ctx.transcriptPath)
63
- folders.push(path.join(path.dirname(ctx.transcriptPath), 'memory'));
64
- return folders;
103
+ return names.map((name) => path.join(projects, name, 'memory'));
104
+ }
105
+ function pinnedProjectDirName(env) {
106
+ const pinned = env.CLAUDE_CODE_PROJECT_DIR_NAME;
107
+ // Claude reads the pinned name only alongside a pinned config folder.
108
+ return env.CLAUDE_CONFIG_DIR && pinned !== undefined && PROJECT_DIR_NAME.test(pinned) ? pinned : null;
65
109
  }
66
110
  function userFolders(ctx, config, warnings) {
67
111
  const dir = autoMemoryDirectory(path.join(config, 'settings.json'), ctx.home, warnings);
@@ -8,6 +8,7 @@ export interface Tally {
8
8
  untagged: number;
9
9
  unchanged: number;
10
10
  adopted: number;
11
+ renamed: number;
11
12
  collapsed: number;
12
13
  retagged: number;
13
14
  handedOver: number;
@@ -2,7 +2,7 @@
2
2
  import { AGENT_MEMORY_TOOLS } from './tools.js';
3
3
  export function emptyTally() {
4
4
  return {
5
- imported: 0, replaced: 0, restored: 0, setAside: 0, untagged: 0, unchanged: 0, adopted: 0, collapsed: 0, retagged: 0,
5
+ imported: 0, replaced: 0, restored: 0, setAside: 0, untagged: 0, unchanged: 0, adopted: 0, renamed: 0, collapsed: 0, retagged: 0,
6
6
  handedOver: 0, duplicate: 0, secret: 0, short: 0, rejected: 0, unread: 0, unreadable: 0, busy: 0,
7
7
  };
8
8
  }
@@ -47,7 +47,7 @@ export function totalTally(report) {
47
47
  const plural = (n, one, many) => `${n} ${n === 1 ? one : many}`;
48
48
  const TALLY_WORDS = {
49
49
  imported: 'new', replaced: 'replaced', restored: 'brought back', setAside: 'set aside', untagged: 'pinned and kept',
50
- unchanged: 'unchanged', adopted: 'taken over from the old Claude import', collapsed: 'duplicates folded', retagged: 'retagged',
50
+ unchanged: 'unchanged', adopted: 'taken over from the old Claude import', renamed: 'moved to the project id', collapsed: 'duplicates folded', retagged: 'retagged',
51
51
  handedOver: 'handed over from the global store', duplicate: 'already stored', secret: 'skipped for a secret', short: 'too short',
52
52
  rejected: 'skipped as rejected', unread: 'files skipped', unreadable: 'unreadable folders', busy: 'busy folders',
53
53
  };
@@ -24,10 +24,10 @@ export declare function importForStore(hippoRoot: string, opts: SyncOptions): Im
24
24
  export declare function importProjectMemories(hippoRoot: string, opts: SyncOptions): ImportReport;
25
25
  /** Every tool's user-level memory into the global store, created on demand, with no origin. */
26
26
  export declare function importUserMemories(invokingRoot: string, opts: SyncOptions): ImportReport;
27
- /** Session end in a folder with no store of its own: the session's project into the global store with its origin, then the user pass. */
27
+ /** Session end in a folder with no store of its own: the session's project into the global store with its origin, the session folder's notes, then the user pass. */
28
28
  export declare function importAtSessionEnd(cwd: string, transcriptPath: string | undefined, opts: SyncOptions): ImportReport;
29
- /** Post-compact: the transcript folder's notes only, with no git call, no legacy adoption and no user pass, as the hook has 10 seconds. */
30
- export declare function importAtCompaction(hippoRoot: string, transcriptPath: string, originProject: string | undefined, opts: SyncOptions): ImportReport;
29
+ /** The session folder's notes under the project Claude filed them for, never cwd's: a session begun at home keeps its home notes user-global wherever it ends. No git call, so post-compact can run it. */
30
+ export declare function importSessionFolder(hippoRoot: string, transcriptPath: string, cwd: string | null, opts: SyncOptions): ImportReport;
31
31
  /** The variable when set (empty or `none` is off); else the tools both the invoking and the target store's config allow. */
32
32
  export declare function allowedTools(env: Machine['env'], invoking: string, target: string, warnings: string[]): Set<ToolId>;
33
33
  //# sourceMappingURL=sync.d.ts.map
@@ -6,7 +6,8 @@ import path from 'node:path';
6
6
  import { errorMessage } from '../log.js';
7
7
  import { loadConfig } from '../config.js';
8
8
  import { closeHippoDb, isSqliteBusy, openHippoDb } from '../db.js';
9
- import { deriveOriginProject, isGlobalStoreRoot, resolveGlobalRootDir } from '../project-identity.js';
9
+ import { namesFoldedInto } from '../project-merge.js';
10
+ import { isGlobalStoreRoot, originInSql, projectNames, resolveGlobalRootDir, resolveProjectIdentity } from '../project-identity.js';
10
11
  import { duplicateKey, heldTextKeys } from '../same-text.js';
11
12
  import { removeEntryMirrors } from '../store/mirrors.js';
12
13
  import { initStore, isInitialized } from '../store/open.js';
@@ -15,7 +16,7 @@ import { selectLiveEntriesBySourcePrefix } from '../store/entry-reads.js';
15
16
  import { updateStats } from '../store/index-and-stats.js';
16
17
  import { resolveTenantId } from '../tenant.js';
17
18
  import { setAsideRow, syncContainer } from './apply.js';
18
- import { claudeCodeAdapter, claudeTranscriptListing } from './claude-code.js';
19
+ import { claudeCodeAdapter, claudeTranscriptListing, transcriptNotesProject } from './claude-code.js';
19
20
  import { codexAdapter } from './codex.js';
20
21
  import { copilotAdapter } from './copilot.js';
21
22
  import { geminiAdapter } from './gemini.js';
@@ -43,34 +44,45 @@ export function importForStore(hippoRoot, opts) {
43
44
  export function importProjectMemories(hippoRoot, opts) {
44
45
  const ctx = context(opts.machine, { projectRoot: path.dirname(hippoRoot) });
45
46
  return runPass({
46
- scope: 'project', target: hippoRoot, invoking: hippoRoot, list: (a) => a.list(ctx, 'project'), legacy: true, originProject: undefined, handover: true,
47
+ scope: 'project', target: hippoRoot, invoking: hippoRoot, list: (a) => a.list(ctx, 'project'), legacy: true, originProject: undefined, legacyOrigins: [], handover: true,
47
48
  }, opts);
48
49
  }
49
50
  /** Every tool's user-level memory into the global store, created on demand, with no origin. */
50
51
  export function importUserMemories(invokingRoot, opts) {
51
52
  const ctx = context(opts.machine, {});
52
53
  return runPass({
53
- scope: 'user', target: resolveGlobalRootDir(), invoking: invokingRoot, list: (a) => a.list(ctx, 'user'), legacy: false, originProject: '', handover: false,
54
+ scope: 'user', target: resolveGlobalRootDir(), invoking: invokingRoot, list: (a) => a.list(ctx, 'user'), legacy: false, originProject: '', legacyOrigins: [], handover: false,
54
55
  }, opts);
55
56
  }
56
- /** Session end in a folder with no store of its own: the session's project into the global store with its origin, then the user pass. */
57
+ /** Session end in a folder with no store of its own: the session's project into the global store with its origin, the session folder's notes, then the user pass. */
57
58
  export function importAtSessionEnd(cwd, transcriptPath, opts) {
58
59
  const globalRoot = resolveGlobalRootDir();
59
- const ctx = context(opts.machine, { projectRoot: cwd, transcriptPath });
60
+ const ctx = context(opts.machine, { projectRoot: cwd });
60
61
  const report = runPass({
61
- scope: 'project', target: globalRoot, invoking: globalRoot, list: (a) => a.list(ctx, 'project'), legacy: false, originProject: deriveOriginProject(cwd), handover: false,
62
+ scope: 'project', target: globalRoot, invoking: globalRoot, list: (a) => a.list(ctx, 'project'), legacy: false, ...projectOrigins(resolveProjectIdentity(cwd)), handover: false,
62
63
  }, opts);
64
+ if (transcriptPath !== undefined)
65
+ mergeReports(report, importSessionFolder(globalRoot, transcriptPath, cwd, opts));
63
66
  mergeReports(report, importUserMemories(globalRoot, opts));
64
67
  return report;
65
68
  }
66
- /** Post-compact: the transcript folder's notes only, with no git call, no legacy adoption and no user pass, as the hook has 10 seconds. */
67
- export function importAtCompaction(hippoRoot, transcriptPath, originProject, opts) {
69
+ /** The session folder's notes under the project Claude filed them for, never cwd's: a session begun at home keeps its home notes user-global wherever it ends. No git call, so post-compact can run it. */
70
+ export function importSessionFolder(hippoRoot, transcriptPath, cwd, opts) {
71
+ const project = transcriptNotesProject(transcriptPath, cwd, opts.machine);
72
+ if (project === null)
73
+ return emptyReport();
74
+ // A project store takes its own project's notes; another project's, or home's, go to the global store, which parts them by origin.
75
+ const own = isGlobalStoreRoot(hippoRoot) || project.name === resolveProjectIdentity(path.dirname(hippoRoot)).name;
76
+ const target = own ? hippoRoot : resolveGlobalRootDir();
68
77
  const ctx = context(opts.machine, {});
69
78
  return runPass({
70
- scope: 'project', target: hippoRoot, invoking: hippoRoot, legacy: false, originProject, handover: false,
79
+ scope: 'project', target, invoking: hippoRoot, legacy: false, ...projectOrigins(project), handover: false,
71
80
  list: (a) => (a.tool === 'claude-code' ? claudeTranscriptListing(ctx, transcriptPath) : null),
72
81
  }, opts);
73
82
  }
83
+ function projectOrigins(project) {
84
+ return { originProject: project.name, legacyOrigins: projectNames(project).slice(1) };
85
+ }
74
86
  function context(machine, extra) {
75
87
  return { home: machine.home, env: machine.env, platform: machine.platform, ...extra };
76
88
  }
@@ -175,11 +187,12 @@ function syncStore(pass, listings, store, opts, report) {
175
187
  tenantId,
176
188
  baseHalfLifeDays: loadConfig(store.root).defaultHalfLifeDays,
177
189
  originProject: pass.originProject,
178
- isDuplicate: duplicateCheck(store, tenantId, pass.originProject, legacy?.adopted ?? new Set()),
190
+ isDuplicate: duplicateCheck(store, tenantId, [pass.originProject ?? '', ...pass.legacyOrigins], legacy?.adopted ?? new Set()),
179
191
  dryRun: opts.dryRun === true,
180
192
  };
181
193
  // A project's rows in the global store are parted by origin: a worktree and its main checkout share a Claude folder.
182
194
  const partition = store.global && pass.scope === 'project' ? pass.originProject ?? '' : null;
195
+ const earlier = partition === null || partition === '' ? [] : earlierNames(store.db, tenantId, partition, pass.legacyOrigins);
183
196
  const synced = [];
184
197
  let remembered = 0;
185
198
  for (const listing of listings) {
@@ -190,7 +203,7 @@ function syncStore(pass, listings, store, opts, report) {
190
203
  out.tally.unreadable++;
191
204
  continue;
192
205
  }
193
- const work = containerWork(tool, container, opts.machine.platform, legacy, partition ?? '');
206
+ const work = containerWork(tool, container, opts.machine.platform, legacy, partition ?? '', earlier);
194
207
  const outcome = syncOne(session, work, out, report);
195
208
  if (outcome === null)
196
209
  continue;
@@ -200,21 +213,28 @@ function syncStore(pass, listings, store, opts, report) {
200
213
  afterCommit(store.root, outcome, report);
201
214
  }
202
215
  if (session.dryRun)
203
- out.unlisted += unlistedRows(store.db, tenantId, tool, pass.scope, listing, opts.machine.platform, partition);
216
+ out.unlisted += unlistedRows(store.db, tenantId, tool, pass.scope, listing, opts.machine.platform, partition === null ? null : [partition, ...earlier]);
204
217
  }
205
218
  if (remembered > 0 && !session.dryRun)
206
219
  bumpRemembered(store.root, remembered, report);
207
220
  return synced;
208
221
  }
209
- function containerWork(tool, container, platform, legacy, origin) {
222
+ /** The legacy names plus every name a merge or repair folded into the project; the audit read runs once per pass. */
223
+ function earlierNames(db, tenantId, id, legacyOrigins) {
224
+ const names = [id, ...legacyOrigins];
225
+ return [...legacyOrigins, ...namesFoldedInto(db, tenantId, names)].filter((n) => n !== '' && n !== id);
226
+ }
227
+ function containerWork(tool, container, platform, legacy, origin, earlier) {
210
228
  const none = new Map();
211
229
  const own = tool.id === 'claude-code' ? legacy?.byContainer.get(container.path) : undefined;
230
+ const prefixOf = (name) => containerPrefix(tool.id, containerId(container.path, container.scope, platform, name));
212
231
  return {
213
232
  tool,
214
233
  container,
215
- prefix: containerPrefix(tool.id, containerId(container.path, container.scope, platform, origin)),
234
+ prefix: prefixOf(origin),
216
235
  adopt: own?.adopt ?? none,
217
236
  replace: own?.replace ?? none,
237
+ legacyPrefixes: earlier.map(prefixOf),
218
238
  };
219
239
  }
220
240
  /** Null when the container was skipped: a busy store waits for the next sync, any other failure is a warning and the sync goes on. */
@@ -257,27 +277,27 @@ function bumpRemembered(root, remembered, report) {
257
277
  }
258
278
  }
259
279
  /** Design 6: only text stored by another path counts, and in the global store only rows visible where the new row goes. */
260
- function duplicateCheck(store, tenantId, origin, adopted) {
280
+ function duplicateCheck(store, tenantId, origins, adopted) {
261
281
  let keys = null;
262
282
  return (text) => {
263
- keys ??= otherPathKeys(store, tenantId, origin ?? '', adopted);
283
+ keys ??= otherPathKeys(store, tenantId, origins, adopted);
264
284
  return keys.has(duplicateKey(text));
265
285
  };
266
286
  }
267
- function otherPathKeys(store, tenantId, origin, adopted) {
268
- const visible = store.global ? ` AND (origin_project = '' OR origin_project = ?)` : '';
269
- const params = store.global ? [tenantId, AGENT_MEMORY_SOURCE_PREFIX, origin] : [tenantId, AGENT_MEMORY_SOURCE_PREFIX];
287
+ function otherPathKeys(store, tenantId, origins, adopted) {
288
+ const visible = store.global ? ` AND (origin_project = '' OR ${originInSql(origins)})` : '';
289
+ const params = store.global ? [tenantId, AGENT_MEMORY_SOURCE_PREFIX, ...origins] : [tenantId, AGENT_MEMORY_SOURCE_PREFIX];
270
290
  // SAFETY: the SELECT names the three columns of the row type.
271
291
  const rows = store.db.prepare(`SELECT id, content, source FROM memories
272
292
  WHERE tenant_id = ? AND superseded_by IS NULL AND substr(source, 1, ${AGENT_MEMORY_SOURCE_PREFIX.length}) != ?${visible}`).all(...params);
273
293
  return new Set(rows.filter((r) => !adopted.has(r.id)).flatMap(heldTextKeys));
274
294
  }
275
- /** Dry run only: kept rows of this scope in containers this run did not list (a moved project's old folder); `partition` limits it to one origin. */
295
+ /** Dry run only: kept rows of this scope in containers this run did not list (a moved project's old folder); `partition` limits it to one project's names. */
276
296
  function unlistedRows(db, tenantId, tool, scope, listing, platform, partition) {
277
- const listed = listing.containers.map((c) => containerPrefix(tool.id, containerId(c.path, c.scope, platform, partition ?? '')));
297
+ const listed = listing.containers.flatMap((c) => (partition ?? ['']).map((name) => containerPrefix(tool.id, containerId(c.path, c.scope, platform, name))));
278
298
  return selectLiveEntriesBySourcePrefix(db, tenantId, `${toolSourcePrefix(tool.id)}${scope === 'project' ? 'p' : 'u'}-`)
279
299
  .filter((row) => row.tags.includes(tool.tag) && !listed.some((p) => row.source.startsWith(p)))
280
- .filter((row) => partition === null || (row.origin_project ?? '') === partition).length;
300
+ .filter((row) => partition === null || partition.includes(row.origin_project ?? '')).length;
281
301
  }
282
302
  /** Design 2's handover: rows the store-less hook path left in the global store, under this project's origin, for containers its store now syncs. */
283
303
  function handOver(synced, projectRoot, opts, report) {
@@ -289,7 +309,9 @@ function handOver(synced, projectRoot, opts, report) {
289
309
  db = openHippoDb(globalRoot, { busyWaitMs: opts.busyWaitMs });
290
310
  const tenantId = resolveTenantId({});
291
311
  // A folder with no git and no marker wrote as '' before its store existed, and as its own name after.
292
- const origins = [...new Set([deriveOriginProject(projectRoot), ''])];
312
+ // Names folded into this project's in the global store were its rows too.
313
+ const names = projectNames(resolveProjectIdentity(projectRoot));
314
+ const origins = [...new Set([...names, ...namesFoldedInto(db, tenantId, names), ''])];
293
315
  for (const work of synced)
294
316
  handOverContainer(db, globalRoot, tenantId, work, origins, opts.machine.platform, report);
295
317
  }
@@ -6,8 +6,6 @@ export interface AdapterContext {
6
6
  readonly env: Readonly<Record<string, string | undefined>>;
7
7
  readonly platform: NodeJS.Platform;
8
8
  readonly projectRoot?: string;
9
- /** Claude Code's transcript at a hook: its folder's `memory/` holds that session's own notes. */
10
- readonly transcriptPath?: string;
11
9
  }
12
10
  export interface MemoryItem {
13
11
  /** A path inside the container with '/' separators, or `<heading slug>/<text hash>` in a single-file store. */