hippo-memory 1.61.0 → 1.62.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +28 -53
- package/dist/agent-memories/apply.d.ts +1 -1
- package/dist/agent-memories/claude-code.d.ts +3 -1
- package/dist/agent-memories/claude-code.js +53 -9
- package/dist/agent-memories/sync.d.ts +3 -3
- package/dist/agent-memories/sync.js +14 -6
- package/dist/agent-memories/types.d.ts +0 -2
- package/dist/api/assemble.js +55 -54
- package/dist/api/context-select.d.ts +49 -0
- package/dist/api/context-select.js +344 -0
- package/dist/api/context.d.ts +2 -2
- package/dist/api/context.js +195 -522
- package/dist/api/drill-down.js +36 -33
- package/dist/api/promote.js +55 -66
- package/dist/api/recall.js +303 -438
- package/dist/api/sleep.js +203 -218
- package/dist/capture/compact.d.ts +1 -1
- package/dist/capture/compact.js +2 -2
- package/dist/cli/briefs.js +324 -306
- package/dist/cli/context.js +44 -34
- package/dist/cli/continuity.js +283 -271
- package/dist/cli/curate.js +35 -34
- package/dist/cli/decisions.js +333 -333
- package/dist/cli/explain.js +66 -60
- package/dist/cli/maintenance.js +62 -51
- package/dist/cli/playbooks.js +387 -370
- package/dist/cli/projects.js +8 -5
- package/dist/cli/recall.js +28 -43
- package/dist/cli/remember.js +113 -70
- package/dist/cli/session-hooks.js +100 -90
- package/dist/cli/setup.js +267 -246
- package/dist/cli/status.js +73 -64
- package/dist/cli/transfer.js +85 -99
- package/dist/compaction-record.d.ts +0 -2
- package/dist/compaction-record.js +1 -1
- package/dist/customer-notes.js +77 -68
- package/dist/dag.js +222 -186
- package/dist/decisions.js +93 -76
- package/dist/doctor.js +11 -7
- package/dist/goals.js +99 -86
- package/dist/incidents.js +45 -38
- package/dist/policies.js +85 -68
- package/dist/processes.js +87 -71
- package/dist/project-briefs.js +135 -108
- package/dist/project-merge.d.ts +12 -4
- package/dist/project-merge.js +130 -45
- package/dist/shared.d.ts +9 -0
- package/dist/shared.js +10 -8
- package/dist/skills.js +81 -65
- package/dist/store/search-rows.d.ts +2 -2
- package/dist/store/search-rows.js +15 -9
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/extensions/openclaw-plugin/openclaw.plugin.json +1 -1
- package/extensions/openclaw-plugin/package.json +1 -1
- package/openclaw.plugin.json +1 -1
- 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.
|
|
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,7 +544,7 @@ 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
|
|
547
|
+
hippo projects repair --global # set aside note copies, fold old worktree names, re-tag merges
|
|
548
548
|
```
|
|
549
549
|
|
|
550
550
|
**See what memory costs in tokens.** Every block of memory text hippo hands an agent (the
|
|
@@ -936,53 +936,36 @@ For how these mechanisms connect to LLM training, continual learning, and open r
|
|
|
936
936
|
|
|
937
937
|
## Comparison
|
|
938
938
|
|
|
939
|
-
|
|
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.
|
|
939
|
+
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
940
|
|
|
945
941
|
| 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
942
|
|---------|-------|-----------|------|-------------|--------|-----|-------|--------|---------|----------|
|
|
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
943
|
| Decay by default | Yes | No | No | No | No | No | No | No | No | No |
|
|
975
944
|
| Retrieval strengthening | Yes | No | No | No | No | No | No | Partial (recall tuning) | No | Partial (Skill Memory distills patterns) |
|
|
976
945
|
| Reward-proportional decay | Yes | No | No | No | No | No | No | No | No | No |
|
|
977
|
-
|
|
|
946
|
+
| 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) |
|
|
947
|
+
| 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
948
|
| 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) |
|
|
949
|
+
| 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
950
|
| Transfer scoring | Yes | No | No | No | No | No | No | No | No | No |
|
|
951
|
+
| Outcome tracking | Yes | No | No | No | No | No | No | No | No | Partial (Cases: agent trajectories) |
|
|
980
952
|
| 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
953
|
| Spatial organization | No | Yes (wings/halls/rooms) | No | No | No | No | No | No | No | No |
|
|
983
954
|
| Lossless compression | No | Yes (AAAK, 30x) | No | No | No | No | No | No | No | No |
|
|
955
|
+
| 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) |
|
|
956
|
+
| Auto-hook install | Yes | No | No | No | No | No | No | No | No | No |
|
|
957
|
+
| 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 | ? |
|
|
958
|
+
| 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) |
|
|
959
|
+
| 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%) |
|
|
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
|
+
| 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
963
|
|
|
985
|
-
|
|
964
|
+
\* 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.
|
|
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
|
+
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
969
|
|
|
987
970
|
---
|
|
988
971
|
|
|
@@ -1090,7 +1073,7 @@ node run.mjs --adapter all
|
|
|
1090
1073
|
|
|
1091
1074
|
### How do I give Claude Code memory between sessions?
|
|
1092
1075
|
|
|
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
|
|
1076
|
+
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
1077
|
|
|
1095
1078
|
### How do I give Cursor memory between sessions?
|
|
1096
1079
|
|
|
@@ -1098,19 +1081,11 @@ Run `npm install -g hippo-memory`, then `hippo init` in the project. If the proj
|
|
|
1098
1081
|
|
|
1099
1082
|
### How do I give Codex memory across sessions?
|
|
1100
1083
|
|
|
1101
|
-
`hippo init` adds its instructions to your `AGENTS.md`, which Codex reads before it starts work.
|
|
1084
|
+
`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
1085
|
|
|
1103
1086
|
### Which agents does hippo work with?
|
|
1104
1087
|
|
|
1105
|
-
`hippo init` detects Claude Code, Codex, Cursor, OpenClaw, OpenCode and Pi
|
|
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.
|
|
1088
|
+
`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
1089
|
|
|
1115
1090
|
### Can I use hippo as an MCP memory server?
|
|
1116
1091
|
|
|
@@ -1118,7 +1093,7 @@ Yes. `hippo mcp` runs the server over stdio, and `npx -y hippo-memory mcp` runs
|
|
|
1118
1093
|
|
|
1119
1094
|
### How is hippo different from mem0?
|
|
1120
1095
|
|
|
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
|
|
1096
|
+
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
1097
|
|
|
1123
1098
|
### Is this just RAG?
|
|
1124
1099
|
|
|
@@ -1126,7 +1101,7 @@ No. RAG searches a fixed corpus. Hippo's store changes as your agent works: a me
|
|
|
1126
1101
|
|
|
1127
1102
|
### Does it need embeddings?
|
|
1128
1103
|
|
|
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,
|
|
1104
|
+
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
1105
|
|
|
1131
1106
|
### Do I still need CLAUDE.md?
|
|
1132
1107
|
|
|
@@ -1138,11 +1113,11 @@ Mark it, and it drops out of the top results. `hippo outcome --bad` weakens the
|
|
|
1138
1113
|
|
|
1139
1114
|
### Where does hippo keep my data?
|
|
1140
1115
|
|
|
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
|
|
1116
|
+
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
1117
|
|
|
1143
1118
|
### What does hippo cost?
|
|
1144
1119
|
|
|
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
|
|
1120
|
+
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
1121
|
|
|
1147
1122
|
### Is it production-ready?
|
|
1148
1123
|
|
|
@@ -1150,7 +1125,7 @@ Judge it by what is tested. 3,500+ tests run against a real database, with no mo
|
|
|
1150
1125
|
|
|
1151
1126
|
### Has hippo been shown to make agents better at their work?
|
|
1152
1127
|
|
|
1153
|
-
|
|
1128
|
+
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
1129
|
|
|
1155
1130
|
---
|
|
1156
1131
|
|
|
@@ -34,7 +34,7 @@ export interface ContainerOutcome {
|
|
|
34
34
|
}
|
|
35
35
|
/** Throws SQLITE_BUSY when another writer holds the store past its busy timeout; nothing is written then. */
|
|
36
36
|
export declare function syncContainer(s: StoreSession, work: ContainerWork): ContainerOutcome;
|
|
37
|
-
export type SetAsideWhy = 'note-gone' | 'note-changed' | 'handover' | 'project-merge';
|
|
37
|
+
export type SetAsideWhy = 'note-gone' | 'note-changed' | 'handover' | 'project-merge' | 'project-repair';
|
|
38
38
|
export type SetAsideResult = {
|
|
39
39
|
readonly kind: 'untagged';
|
|
40
40
|
readonly entry: MemoryEntry;
|
|
@@ -6,6 +6,8 @@ export declare function claudeCheckoutRoot(top: string, gitDir: string, common:
|
|
|
6
6
|
/** 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
7
|
export declare function claudeFolderName(root: string): string;
|
|
8
8
|
export declare const claudeCodeAdapter: Adapter;
|
|
9
|
-
/**
|
|
9
|
+
/** 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
10
|
export declare function claudeTranscriptListing(ctx: AdapterContext, transcriptPath: string): Listing;
|
|
11
|
+
/** 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. */
|
|
12
|
+
export declare function transcriptNotesOrigin(transcriptPath: string, cwd: string | null, machine: Pick<AdapterContext, 'platform' | 'env'>): string | null;
|
|
11
13
|
//# 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 { deriveOriginProject, realpathOrResolve } 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
|
-
/**
|
|
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 transcriptNotesOrigin(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) ? deriveOriginProject(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) ? deriveOriginProject(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
|
|
58
|
-
|
|
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
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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);
|
|
@@ -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
|
-
/**
|
|
30
|
-
export declare function
|
|
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
|
|
@@ -15,7 +15,7 @@ import { selectLiveEntriesBySourcePrefix } from '../store/entry-reads.js';
|
|
|
15
15
|
import { updateStats } from '../store/index-and-stats.js';
|
|
16
16
|
import { resolveTenantId } from '../tenant.js';
|
|
17
17
|
import { setAsideRow, syncContainer } from './apply.js';
|
|
18
|
-
import { claudeCodeAdapter, claudeTranscriptListing } from './claude-code.js';
|
|
18
|
+
import { claudeCodeAdapter, claudeTranscriptListing, transcriptNotesOrigin } from './claude-code.js';
|
|
19
19
|
import { codexAdapter } from './codex.js';
|
|
20
20
|
import { copilotAdapter } from './copilot.js';
|
|
21
21
|
import { geminiAdapter } from './gemini.js';
|
|
@@ -53,21 +53,29 @@ export function importUserMemories(invokingRoot, opts) {
|
|
|
53
53
|
scope: 'user', target: resolveGlobalRootDir(), invoking: invokingRoot, list: (a) => a.list(ctx, 'user'), legacy: false, originProject: '', handover: false,
|
|
54
54
|
}, opts);
|
|
55
55
|
}
|
|
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. */
|
|
56
|
+
/** 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
57
|
export function importAtSessionEnd(cwd, transcriptPath, opts) {
|
|
58
58
|
const globalRoot = resolveGlobalRootDir();
|
|
59
|
-
const ctx = context(opts.machine, { projectRoot: cwd
|
|
59
|
+
const ctx = context(opts.machine, { projectRoot: cwd });
|
|
60
60
|
const report = runPass({
|
|
61
61
|
scope: 'project', target: globalRoot, invoking: globalRoot, list: (a) => a.list(ctx, 'project'), legacy: false, originProject: deriveOriginProject(cwd), handover: false,
|
|
62
62
|
}, opts);
|
|
63
|
+
if (transcriptPath !== undefined)
|
|
64
|
+
mergeReports(report, importSessionFolder(globalRoot, transcriptPath, cwd, opts));
|
|
63
65
|
mergeReports(report, importUserMemories(globalRoot, opts));
|
|
64
66
|
return report;
|
|
65
67
|
}
|
|
66
|
-
/**
|
|
67
|
-
export function
|
|
68
|
+
/** 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. */
|
|
69
|
+
export function importSessionFolder(hippoRoot, transcriptPath, cwd, opts) {
|
|
70
|
+
const origin = transcriptNotesOrigin(transcriptPath, cwd, opts.machine);
|
|
71
|
+
if (origin === null)
|
|
72
|
+
return emptyReport();
|
|
73
|
+
// 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.
|
|
74
|
+
const own = isGlobalStoreRoot(hippoRoot) || origin === deriveOriginProject(path.dirname(hippoRoot));
|
|
75
|
+
const target = own ? hippoRoot : resolveGlobalRootDir();
|
|
68
76
|
const ctx = context(opts.machine, {});
|
|
69
77
|
return runPass({
|
|
70
|
-
scope: 'project', target
|
|
78
|
+
scope: 'project', target, invoking: hippoRoot, legacy: false, originProject: origin, handover: false,
|
|
71
79
|
list: (a) => (a.tool === 'claude-code' ? claudeTranscriptListing(ctx, transcriptPath) : null),
|
|
72
80
|
}, opts);
|
|
73
81
|
}
|
|
@@ -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. */
|
package/dist/api/assemble.js
CHANGED
|
@@ -59,58 +59,9 @@ export function assemble(ctx, sessionId, opts = {}) {
|
|
|
59
59
|
const olderRows = scoped.slice(0, tailStartIdx);
|
|
60
60
|
const tailRows = scoped.slice(tailStartIdx);
|
|
61
61
|
// Substitute parent summaries for older rows that share one.
|
|
62
|
-
const olderItems =
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
const olderByParent = new Map();
|
|
66
|
-
for (const r of olderRows) {
|
|
67
|
-
if (!r.dag_parent_id)
|
|
68
|
-
continue;
|
|
69
|
-
const list = olderByParent.get(r.dag_parent_id) ?? [];
|
|
70
|
-
list.push(r);
|
|
71
|
-
olderByParent.set(r.dag_parent_id, list);
|
|
72
|
-
}
|
|
73
|
-
const eligibleParentIds = Array.from(olderByParent.keys()).filter((pid) => (olderByParent.get(pid)?.length ?? 0) >= 2);
|
|
74
|
-
const parents = eligibleParentIds.length > 0
|
|
75
|
-
? loadEntriesByIds(ctx.hippoRoot, eligibleParentIds, ctx.tenantId)
|
|
76
|
-
.filter((p) => (p.dag_level ?? 0) === 2 && !p.superseded_by)
|
|
77
|
-
.filter((p) => passesScopeFilterForRecall(p.scope ?? null, opts.scope))
|
|
78
|
-
: [];
|
|
79
|
-
const claimedRawIds = new Set();
|
|
80
|
-
for (const parent of parents) {
|
|
81
|
-
const claimed = (olderByParent.get(parent.id) ?? []).map((r) => r.id);
|
|
82
|
-
claimed.forEach((id) => claimedRawIds.add(id));
|
|
83
|
-
olderItems.push({
|
|
84
|
-
id: parent.id,
|
|
85
|
-
content: parent.content,
|
|
86
|
-
createdAt: parent.earliest_at ?? parent.created,
|
|
87
|
-
isSummary: true,
|
|
88
|
-
substitutedFor: claimed,
|
|
89
|
-
strength: parent.strength,
|
|
90
|
-
});
|
|
91
|
-
summarized += claimed.length;
|
|
92
|
-
}
|
|
93
|
-
for (const r of olderRows) {
|
|
94
|
-
if (claimedRawIds.has(r.id))
|
|
95
|
-
continue;
|
|
96
|
-
olderItems.push({
|
|
97
|
-
id: r.id,
|
|
98
|
-
content: r.content,
|
|
99
|
-
createdAt: r.created,
|
|
100
|
-
strength: r.strength,
|
|
101
|
-
});
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
else {
|
|
105
|
-
for (const r of olderRows) {
|
|
106
|
-
olderItems.push({
|
|
107
|
-
id: r.id,
|
|
108
|
-
content: r.content,
|
|
109
|
-
createdAt: r.created,
|
|
110
|
-
strength: r.strength,
|
|
111
|
-
});
|
|
112
|
-
}
|
|
113
|
-
}
|
|
62
|
+
const { olderItems, summarized } = summarizeOlder && olderRows.length > 0
|
|
63
|
+
? substituteSummaries(ctx, olderRows, opts.scope)
|
|
64
|
+
: { olderItems: olderRows.map(rawItem), summarized: 0 };
|
|
114
65
|
const tailItems = tailRows.map((r) => ({
|
|
115
66
|
id: r.id,
|
|
116
67
|
content: r.content,
|
|
@@ -124,9 +75,59 @@ export function assemble(ctx, sessionId, opts = {}) {
|
|
|
124
75
|
const cmpIso = (a, b) => (a < b ? -1 : a > b ? 1 : 0);
|
|
125
76
|
olderItems.sort((a, b) => cmpIso(a.createdAt, b.createdAt));
|
|
126
77
|
tailItems.sort((a, b) => cmpIso(a.createdAt, b.createdAt));
|
|
127
|
-
let items = [...olderItems, ...tailItems];
|
|
128
78
|
const itemCost = opts.cost?.item ?? ((it) => estimateTokens(it.content));
|
|
129
79
|
const room = budget - (opts.cost?.fixed(Math.max(budget, totalRaw)) ?? 0);
|
|
80
|
+
const { items, tokens, evicted } = evictToBudget([...olderItems, ...tailItems], itemCost, room);
|
|
81
|
+
return { sessionId, items, tokens, totalRaw, summarized, evicted, truncated };
|
|
82
|
+
}
|
|
83
|
+
function rawItem(r) {
|
|
84
|
+
return {
|
|
85
|
+
id: r.id,
|
|
86
|
+
content: r.content,
|
|
87
|
+
createdAt: r.created,
|
|
88
|
+
strength: r.strength,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
function substituteSummaries(ctx, olderRows, scope) {
|
|
92
|
+
const olderItems = [];
|
|
93
|
+
let summarized = 0;
|
|
94
|
+
const olderByParent = new Map();
|
|
95
|
+
for (const r of olderRows) {
|
|
96
|
+
if (!r.dag_parent_id)
|
|
97
|
+
continue;
|
|
98
|
+
const list = olderByParent.get(r.dag_parent_id) ?? [];
|
|
99
|
+
list.push(r);
|
|
100
|
+
olderByParent.set(r.dag_parent_id, list);
|
|
101
|
+
}
|
|
102
|
+
const eligibleParentIds = Array.from(olderByParent.keys()).filter((pid) => (olderByParent.get(pid)?.length ?? 0) >= 2);
|
|
103
|
+
const parents = eligibleParentIds.length > 0
|
|
104
|
+
? loadEntriesByIds(ctx.hippoRoot, eligibleParentIds, ctx.tenantId)
|
|
105
|
+
.filter((p) => (p.dag_level ?? 0) === 2 && !p.superseded_by)
|
|
106
|
+
.filter((p) => passesScopeFilterForRecall(p.scope ?? null, scope))
|
|
107
|
+
: [];
|
|
108
|
+
const claimedRawIds = new Set();
|
|
109
|
+
for (const parent of parents) {
|
|
110
|
+
const claimed = (olderByParent.get(parent.id) ?? []).map((r) => r.id);
|
|
111
|
+
claimed.forEach((id) => claimedRawIds.add(id));
|
|
112
|
+
olderItems.push({
|
|
113
|
+
id: parent.id,
|
|
114
|
+
content: parent.content,
|
|
115
|
+
createdAt: parent.earliest_at ?? parent.created,
|
|
116
|
+
isSummary: true,
|
|
117
|
+
substitutedFor: claimed,
|
|
118
|
+
strength: parent.strength,
|
|
119
|
+
});
|
|
120
|
+
summarized += claimed.length;
|
|
121
|
+
}
|
|
122
|
+
for (const r of olderRows) {
|
|
123
|
+
if (claimedRawIds.has(r.id))
|
|
124
|
+
continue;
|
|
125
|
+
olderItems.push(rawItem(r));
|
|
126
|
+
}
|
|
127
|
+
return { olderItems, summarized };
|
|
128
|
+
}
|
|
129
|
+
function evictToBudget(start, itemCost, room) {
|
|
130
|
+
let items = start;
|
|
130
131
|
let tokens = items.reduce((acc, it) => acc + itemCost(it), 0);
|
|
131
132
|
let evicted = 0;
|
|
132
133
|
while (tokens > room && items.length > 0) {
|
|
@@ -147,6 +148,6 @@ export function assemble(ctx, sessionId, opts = {}) {
|
|
|
147
148
|
tokens -= cost;
|
|
148
149
|
evicted++;
|
|
149
150
|
}
|
|
150
|
-
return {
|
|
151
|
+
return { items, tokens, evicted };
|
|
151
152
|
}
|
|
152
153
|
//# sourceMappingURL=assemble.js.map
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { AmbientLoadResult } from '../store/candidates.js';
|
|
2
|
+
import { type MemoryEntry } from '../memory.js';
|
|
3
|
+
import { type HippoConfig } from '../config.js';
|
|
4
|
+
import type { DeliveryObserver } from '../delivery-recorder.js';
|
|
5
|
+
import type { ContextCost, ContextOpts, ContextResultEntry } from './context-types.js';
|
|
6
|
+
import type { Context } from './types.js';
|
|
7
|
+
/** What getContext resolved from opts and config before it read a row. */
|
|
8
|
+
export interface ContextPlan {
|
|
9
|
+
pinnedOnly: boolean;
|
|
10
|
+
limit: number;
|
|
11
|
+
includeRecent: number;
|
|
12
|
+
activeScope: string;
|
|
13
|
+
exactScope: string | undefined;
|
|
14
|
+
query: string;
|
|
15
|
+
hasLocal: boolean;
|
|
16
|
+
hasGlobal: boolean;
|
|
17
|
+
globalRoot: string;
|
|
18
|
+
primaryIsGlobal: boolean;
|
|
19
|
+
hasLocalTaskState: boolean;
|
|
20
|
+
config: HippoConfig;
|
|
21
|
+
currentProjectName: string;
|
|
22
|
+
includeCrossProject: boolean;
|
|
23
|
+
originProject: string | undefined;
|
|
24
|
+
promptRecallPending: boolean;
|
|
25
|
+
cost: ContextCost | undefined;
|
|
26
|
+
price: (entry: MemoryEntry, isGlobal: boolean, promptRecall?: boolean) => number;
|
|
27
|
+
obs: DeliveryObserver | undefined;
|
|
28
|
+
}
|
|
29
|
+
/** The ambient admit rules; `digestHidden` is read late because a prompt-recall eligibility check can still set it. */
|
|
30
|
+
export interface ContextAdmission {
|
|
31
|
+
ambientAdmit: (e: MemoryEntry) => boolean;
|
|
32
|
+
admit: (e: MemoryEntry) => boolean;
|
|
33
|
+
/** What the two-store search admits: `admit` without the own-session compaction rule. */
|
|
34
|
+
bothStoresAdmit: (e: MemoryEntry) => boolean;
|
|
35
|
+
digestHidden: () => boolean;
|
|
36
|
+
}
|
|
37
|
+
export interface ContextPools {
|
|
38
|
+
local: AmbientLoadResult;
|
|
39
|
+
global: AmbientLoadResult;
|
|
40
|
+
}
|
|
41
|
+
export declare function oneCopyPerMemory(local: readonly MemoryEntry[], global: readonly MemoryEntry[], now: Date): [MemoryEntry[], MemoryEntry[]];
|
|
42
|
+
export declare const finiteOr: (v: number, dflt: number, min: number) => number;
|
|
43
|
+
/** Pins plus the prompt-recall or recent-N backfill; null means the block is empty. */
|
|
44
|
+
export declare function selectPinned(ctx: Context, opts: ContextOpts, plan: ContextPlan, left: number, pools: ContextPools, admission: ContextAdmission): ContextResultEntry[] | null;
|
|
45
|
+
/** No query: the strongest memories by strength, up to budget. */
|
|
46
|
+
export declare function selectStrongest(plan: ContextPlan, left: number, pools: ContextPools): ContextResultEntry[];
|
|
47
|
+
/** Real query: hybrid search over both stores, or physics/hybrid over the local rows; emits the 'recall' audit row. */
|
|
48
|
+
export declare function selectBySearch(ctx: Context, plan: ContextPlan, left: number, pools: ContextPools, admission: ContextAdmission): Promise<ContextResultEntry[]>;
|
|
49
|
+
//# sourceMappingURL=context-select.d.ts.map
|