@bcmyguest/openmem 0.0.0-stage → 0.8.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 (230) hide show
  1. package/.agents/skills/openmem/SKILL.md +34 -0
  2. package/LICENSE +190 -0
  3. package/README.md +140 -2
  4. package/bin/hindsight-project-index +530 -0
  5. package/cli/coding-agent/access-policy.mjs +38 -0
  6. package/cli/coding-agent/agents.mjs +34 -0
  7. package/cli/coding-agent/bank-from-cwd.mjs +140 -0
  8. package/cli/coding-agent/capabilities.mjs +34 -0
  9. package/cli/coding-agent/context-store.mjs +81 -0
  10. package/cli/coding-agent/context-trim.mjs +897 -0
  11. package/cli/coding-agent/file-io.mjs +72 -0
  12. package/cli/coding-agent/hook-harnesses.mjs +88 -0
  13. package/cli/coding-agent/hook-runner.mjs +284 -0
  14. package/cli/coding-agent/install-access.mjs +119 -0
  15. package/cli/coding-agent/install-context-proxy.mjs +333 -0
  16. package/cli/coding-agent/install-rtk.mjs +60 -0
  17. package/cli/coding-agent/install-shim.mjs +201 -0
  18. package/cli/coding-agent/opencode-config.mjs +107 -0
  19. package/cli/coding-agent/opencode-plugin.mjs +132 -0
  20. package/cli/coding-agent/opencode2-plugin/index.js +299 -0
  21. package/cli/coding-agent/recall-frame.mjs +29 -0
  22. package/cli/coding-agent/remove-upstream-access.mjs +122 -0
  23. package/cli/coding-agent/runtime.mjs +40 -0
  24. package/cli/coding-agent/session-db.mjs +626 -0
  25. package/cli/coding-agent/session-facade.mjs +86 -0
  26. package/cli/coding-agent/toml-edit.mjs +161 -0
  27. package/cli/cognee-mcp.mjs +260 -0
  28. package/cli/context-management.mjs +254 -0
  29. package/cli/feed-sync.mjs +27 -0
  30. package/cli/install-coding-agent.mjs +298 -0
  31. package/cli/install-memory-mcp.mjs +426 -0
  32. package/cli/is-main.mjs +18 -0
  33. package/cli/mcp/context-sql-runner.mjs +58 -0
  34. package/cli/mcp/context-store.mjs +670 -0
  35. package/cli/mcp/context.mjs +61 -0
  36. package/cli/mcp/export/cognee-dataset-name.mjs +12 -0
  37. package/cli/mcp/export/cognee.mjs +119 -0
  38. package/cli/mcp/export/hindsight.mjs +48 -0
  39. package/cli/mcp/export/index.mjs +26 -0
  40. package/cli/mcp/export/mem0.mjs +31 -0
  41. package/cli/mcp/knowledge.mjs +333 -0
  42. package/cli/mcp/llm-synthesize.mjs +50 -0
  43. package/cli/mcp/mem0-scope-id.mjs +22 -0
  44. package/cli/mcp/memory-facade.mjs +521 -0
  45. package/cli/mcp/memory-reflect-consolidate.mjs +120 -0
  46. package/cli/mcp/notebooklm-banks.mjs +21 -0
  47. package/cli/mcp/notebooklm-hindsight-rest.mjs +45 -0
  48. package/cli/mcp/notebooklm-provider.mjs +225 -0
  49. package/cli/mcp/pmm-records.mjs +23 -0
  50. package/cli/mcp/portability.mjs +158 -0
  51. package/cli/mcp/stdio.mjs +65 -0
  52. package/cli/mcp/unified-mcp-install.mjs +65 -0
  53. package/cli/mem0-mcp.mjs +108 -0
  54. package/cli/notebooklm-mcp.mjs +236 -0
  55. package/cli/openmem-mcp.mjs +38 -0
  56. package/cli/openmem.mjs +222 -0
  57. package/cli/roots.mjs +85 -0
  58. package/cli/schedule.mjs +176 -0
  59. package/cli/stack.mjs +438 -0
  60. package/cli/switch-memory-provider.mjs +358 -0
  61. package/cli/wiring-report.mjs +125 -0
  62. package/compose.bench.yaml +12 -0
  63. package/compose.yaml +227 -0
  64. package/config/coding-agent.json +13 -0
  65. package/config/hindsight.env.example +26 -0
  66. package/config/index.yml +54 -0
  67. package/config/litellm.lemond.yaml +40 -0
  68. package/config/litellm.ollama.yaml +33 -0
  69. package/config/notebooklm.yml +33 -0
  70. package/config/openmem.toml.example +124 -0
  71. package/docs/OPENMEM.md +23 -0
  72. package/docs/architecture.md +245 -0
  73. package/docs/assets/README.md +10 -0
  74. package/docs/assets/setup-demo.gif +0 -0
  75. package/docs/coding-agent-hooks.md +75 -0
  76. package/docs/coding-agent-scopes.md +22 -0
  77. package/docs/coding-agents.md +71 -0
  78. package/docs/cognee.md +37 -0
  79. package/docs/compatibility.md +80 -0
  80. package/docs/concepts.md +135 -0
  81. package/docs/context-management.md +255 -0
  82. package/docs/context-proxy.md +115 -0
  83. package/docs/context-research.md +215 -0
  84. package/docs/context-store.md +277 -0
  85. package/docs/hindsight.md +77 -0
  86. package/docs/index-sources-roadmap.md +38 -0
  87. package/docs/index-sources.md +39 -0
  88. package/docs/indexer-sinks.md +122 -0
  89. package/docs/indexing.md +57 -0
  90. package/docs/manual-stack-setup.md +74 -0
  91. package/docs/mcp-install-guide.md +4 -0
  92. package/docs/mcp-setup.md +104 -0
  93. package/docs/mem0.md +141 -0
  94. package/docs/memory-portability.md +73 -0
  95. package/docs/memory-providers.md +246 -0
  96. package/docs/meridian-litellm.md +198 -0
  97. package/docs/model-configuration.md +106 -0
  98. package/docs/notebooklm.md +83 -0
  99. package/docs/npx-install.md +172 -0
  100. package/docs/openmem-config.md +194 -0
  101. package/docs/provider-wizard-hooks.md +222 -0
  102. package/docs/quickstart.md +107 -0
  103. package/docs/setup-wizard.md +117 -0
  104. package/docs/sources-feeds.md +39 -0
  105. package/docs/sources-readwise.md +23 -0
  106. package/docs/sources-zotero.md +22 -0
  107. package/glama.json +4 -0
  108. package/indexer/Dockerfile +38 -0
  109. package/indexer/code_outline.py +146 -0
  110. package/indexer/feed_sync.py +107 -0
  111. package/indexer/index_config.py +290 -0
  112. package/indexer/pytest.ini +5 -0
  113. package/indexer/sinks/__init__.py +148 -0
  114. package/indexer/sinks/_http.py +44 -0
  115. package/indexer/sinks/cognee.py +329 -0
  116. package/indexer/sinks/hindsight.py +107 -0
  117. package/indexer/sinks/mem0.py +247 -0
  118. package/indexer/watch.py +240 -0
  119. package/package.json +96 -4
  120. package/providers/_template/compose.yaml +52 -0
  121. package/providers/cognee/OPERATOR.md +82 -0
  122. package/providers/cognee/compose.yaml +89 -0
  123. package/providers/cognee/lib/clack.mjs +157 -0
  124. package/providers/cognee/lib/coding-agent.mjs +26 -0
  125. package/providers/cognee/lib/cognee-mcp-install.mjs +153 -0
  126. package/providers/cognee/lib/config.mjs +98 -0
  127. package/providers/cognee/lib/env-file.mjs +194 -0
  128. package/providers/cognee/lib/env-setup.mjs +83 -0
  129. package/providers/cognee/lib/llm-env.mjs +382 -0
  130. package/providers/cognee/lib/ports.mjs +25 -0
  131. package/providers/cognee/lib/smoke.mjs +246 -0
  132. package/providers/cognee/lib/stack-env.mjs +134 -0
  133. package/providers/cognee/lib/state.mjs +101 -0
  134. package/providers/cognee/lib/summary.mjs +56 -0
  135. package/providers/cognee/lib/validate.mjs +97 -0
  136. package/providers/cognee/manifest.json +56 -0
  137. package/providers/cognee/wizard.mjs +134 -0
  138. package/providers/graphiti/manifest.json +15 -0
  139. package/providers/hindsight/lib/clack.mjs +347 -0
  140. package/providers/hindsight/lib/coding-agent.mjs +204 -0
  141. package/providers/hindsight/lib/config.mjs +79 -0
  142. package/providers/hindsight/lib/env-setup.mjs +72 -0
  143. package/providers/hindsight/lib/litellm-routes.mjs +196 -0
  144. package/providers/hindsight/lib/meridian.mjs +307 -0
  145. package/providers/hindsight/lib/model-backend.mjs +243 -0
  146. package/providers/hindsight/lib/notebooklm-mcp-install.mjs +127 -0
  147. package/providers/hindsight/lib/ollama-compose.mjs +51 -0
  148. package/providers/hindsight/lib/ports.mjs +30 -0
  149. package/providers/hindsight/lib/probe.mjs +54 -0
  150. package/providers/hindsight/lib/stack-env.mjs +125 -0
  151. package/providers/hindsight/lib/state.mjs +80 -0
  152. package/providers/hindsight/lib/summary.mjs +57 -0
  153. package/providers/hindsight/lib/validate.mjs +81 -0
  154. package/providers/hindsight/manifest.json +58 -0
  155. package/providers/hindsight/wizard.mjs +150 -0
  156. package/providers/letta/manifest.json +29 -0
  157. package/providers/mem0/OPERATOR.md +143 -0
  158. package/providers/mem0/compose.yaml +79 -0
  159. package/providers/mem0/lib/coding-agent.mjs +58 -0
  160. package/providers/mem0/lib/config.mjs +45 -0
  161. package/providers/mem0/lib/configure.mjs +201 -0
  162. package/providers/mem0/lib/dotenv.mjs +71 -0
  163. package/providers/mem0/lib/env-file.mjs +110 -0
  164. package/providers/mem0/lib/http.mjs +69 -0
  165. package/providers/mem0/lib/litellm.mjs +457 -0
  166. package/providers/mem0/lib/mcp-install.mjs +165 -0
  167. package/providers/mem0/lib/secret.mjs +30 -0
  168. package/providers/mem0/lib/smoke.mjs +179 -0
  169. package/providers/mem0/lib/stack-env.mjs +255 -0
  170. package/providers/mem0/lib/state.mjs +67 -0
  171. package/providers/mem0/lib/summary.mjs +88 -0
  172. package/providers/mem0/lib/url.mjs +106 -0
  173. package/providers/mem0/lib/validate.mjs +158 -0
  174. package/providers/mem0/manifest.json +47 -0
  175. package/providers/mem0/wizard.mjs +146 -0
  176. package/providers/zep/manifest.json +29 -0
  177. package/proxy/config.yaml +33 -0
  178. package/proxy/openmem_context/__init__.py +6 -0
  179. package/proxy/openmem_context/adapters.py +504 -0
  180. package/proxy/openmem_context/compress.py +53 -0
  181. package/proxy/openmem_context/core.py +606 -0
  182. package/proxy/openmem_context/embed.py +212 -0
  183. package/proxy/openmem_context/recall.py +49 -0
  184. package/proxy/openmem_context/session.py +70 -0
  185. package/proxy/openmem_context/sessiondb.py +276 -0
  186. package/proxy/openmem_context/settings.py +134 -0
  187. package/proxy/openmem_context/store.py +129 -0
  188. package/proxy/openmem_context/tokens.py +123 -0
  189. package/proxy/openmem_context/ttl.py +78 -0
  190. package/proxy/openmem_context_hook.py +34 -0
  191. package/scripts/check-pack.mjs +219 -0
  192. package/scripts/gen-masks.mjs +83 -0
  193. package/scripts/gen-masks.sh +4 -0
  194. package/scripts/promotion-provider-smoke.mjs +34 -0
  195. package/scripts/run-setup.sh +30 -0
  196. package/scripts/setup.mjs +107 -0
  197. package/scripts/setup.sh +5 -0
  198. package/scripts/spin-up.sh +45 -0
  199. package/scripts/wizard.sh +5 -0
  200. package/server.json +34 -0
  201. package/sync/Dockerfile +27 -0
  202. package/sync/memory_backend.py +87 -0
  203. package/sync/notebooklm_sync.py +664 -0
  204. package/wizard/bin/pm.mjs +97 -0
  205. package/wizard/bin/setup.mjs +33 -0
  206. package/wizard/lib/compose-version.mjs +32 -0
  207. package/wizard/lib/compose.mjs +70 -0
  208. package/wizard/lib/context-step.mjs +140 -0
  209. package/wizard/lib/curl.mjs +103 -0
  210. package/wizard/lib/docker-build.mjs +148 -0
  211. package/wizard/lib/exec.mjs +41 -0
  212. package/wizard/lib/indexer.mjs +6 -0
  213. package/wizard/lib/litellm-env.mjs +55 -0
  214. package/wizard/lib/load-machine-env.mjs +4 -0
  215. package/wizard/lib/log.mjs +32 -0
  216. package/wizard/lib/notebooklm-agent-access.mjs +92 -0
  217. package/wizard/lib/notebooklm.mjs +214 -0
  218. package/wizard/lib/parse-args.mjs +87 -0
  219. package/wizard/lib/paths.mjs +39 -0
  220. package/wizard/lib/pm-config.mjs +371 -0
  221. package/wizard/lib/ports.mjs +94 -0
  222. package/wizard/lib/prompts.mjs +85 -0
  223. package/wizard/lib/provider-wizard/helpers.mjs +98 -0
  224. package/wizard/lib/provider-wizard/run-steps.mjs +129 -0
  225. package/wizard/lib/providers/registry.mjs +262 -0
  226. package/wizard/lib/run-clack.mjs +108 -0
  227. package/wizard/lib/run-wizard.mjs +750 -0
  228. package/wizard/lib/validate-setup.mjs +62 -0
  229. package/wizard/lib/wizard-action.mjs +34 -0
  230. package/wizard/lib/wizard-conf.mjs +71 -0
@@ -0,0 +1,73 @@
1
+ # Portable memory (PMM)
2
+
3
+ OpenMem’s **portability layer** moves memories between providers without baking in a vendor dump format. **PMM** (Personal Memory Manifest) is the first interchange format: export what a backend stores, import into another (or the same) provider, and keep a portable copy for backup or “give me my data” requests.
4
+
5
+ `npx @bcmyguest/openmem switch` rewires the stack only — it does **not** migrate content. Use export → switch → import (or re-index) when you change backends.
6
+
7
+ Status: **`memory_export` / `memory_import`** on the unified MCP are **beta**. PMM v1 is stable; adapters live under `cli/mcp/export/` (orchestrator: `cli/mcp/portability.mjs`). MCP install: [MCP setup](mcp-setup.md).
8
+
9
+ ## PMM format (version 1)
10
+
11
+ JSON object, or JSONL with one header line followed by one normalized record per line:
12
+
13
+ | Field | Type | Meaning |
14
+ | --- | --- | --- |
15
+ | `pmm_version` | `1` | Schema version |
16
+ | `exported_at` | ISO-8601 string | When the export was taken |
17
+ | `source_provider` | string | `hindsight` \| `mem0` \| `cognee` |
18
+ | `scope` | object | Provider-specific filter (bank, `user_id`, datasets, …) |
19
+ | `records` | array | Normalized memory records (see below) |
20
+
21
+ Each **record**:
22
+
23
+ | Field | Type | Meaning |
24
+ | --- | --- | --- |
25
+ | `id` | string \| null | Provider id when known |
26
+ | `content` | string | Primary text |
27
+ | `metadata` | object | Optional tags, timestamps, provider payload |
28
+ | `scope_key` | string | Bank name, mem0 `user_id`, Cognee dataset, etc. |
29
+
30
+ Import accepts PMM with `merge_strategy`:
31
+
32
+ | Strategy | Behavior |
33
+ | --- | --- |
34
+ | `skip_duplicates` (default) | For mem0, skip records whose id or content fingerprint already exists in the same bank |
35
+ | `overwrite` | Re-import all records (may duplicate on backends without idempotent upsert) |
36
+
37
+ **Cross-provider import** (e.g. PMM from mem0 into Hindsight) is best-effort: we map `content` + `scope_key` into the active provider’s add API; embeddings and graph structure are **not** preserved.
38
+
39
+ The MCP export currently collects all records in memory before returning them, including with `format: "jsonl"`. Use a bank filter for large stores; the MCP response is not a file stream. Hindsight export reads the memory-list endpoint and does not include knowledge pages, bank configuration, or source documents. Confirm migrated data before switching providers.
40
+
41
+ ## Implementation (orchestrator + adapters)
42
+
43
+ openmem stays a **meta orchestrator**: PMM shape and import live in `cli/mcp/portability.mjs`; each backend implements **list/map** in `cli/mcp/export/` (`hindsight.mjs`, `mem0.mjs`, `cognee.mjs`). New providers add an adapter and one branch in `cli/mcp/export/index.mjs` — no changes to the MCP tool names agents call.
44
+
45
+ ## Unified MCP tools
46
+
47
+ On the `openmem` MCP server ([MCP setup](mcp-setup.md), [Architecture](architecture.md)):
48
+
49
+ ```text
50
+ memory_export { format?: "json"|"jsonl", bank?: string, user_id?: string }
51
+ memory_import { data: string, merge_strategy?: "skip_duplicates"|"overwrite" }
52
+ ```
53
+
54
+ Example flow (Cursor Agent / any host with the unified server):
55
+
56
+ 1. `memory_export` with `format: "json"` and optional `bank`
57
+ 2. `npx @bcmyguest/openmem switch <other> --yes`
58
+ 3. `memory_import` with the exported JSON and `merge_strategy: "skip_duplicates"`
59
+
60
+ Restart Cursor Agent after `npx @bcmyguest/openmem install-mcp --yes`.
61
+
62
+ ## Provider behavior
63
+
64
+ | Provider | Export | Import |
65
+ | --- | --- | --- |
66
+ | **mem0** | `GET /memories` with `user_id` from `bank` arg (API key from env file) | `POST /memories` per record (`infer: false`) |
67
+ | **Hindsight** | `GET /v1/default/banks` then paginated `GET …/memories/list` per bank (`bank` arg = one bank; omit = all banks) | `POST …/banks/{bank}/memories` per record |
68
+ | **Cognee** | Resolve indexer dataset name from `bank`, `GET /api/v1/datasets`, paginate `GET …/data` (label/name as content; not full file bytes) | `POST /api/v1/add` per record when dataset in scope |
69
+
70
+ ## Related
71
+
72
+ - [Architecture — provider interface](architecture.md#how-openmem-interfaces-with-providers)
73
+ - [mem0](mem0.md) · [Cognee](cognee.md)
@@ -0,0 +1,246 @@
1
+ # Pluggable memory providers
2
+
3
+ This repo ships **one** supported backend: [Hindsight](https://github.com/vectorize-io/hindsight)
4
+ (API + retain/reflect/consolidate, LiteLLM in front of local models). **mem0** and **Cognee** are
5
+ **experimental** plugins (indexer sink, compose profile, wizard hooks, stub tests). Wave 3 hardened
6
+ operator DX (coding-agent steps, secrets, Ollama wiring); live Cognee cognify+search with a small
7
+ local model is not yet a promotion bar — see `docs/WAVE3-orchestrator.md`. Manifests under
8
+ `providers/` show `experimental` vs `supported`.
9
+
10
+ ## Meta setup wizard (what this is — and is not)
11
+
12
+ `npm run setup` (checkout), `npx @bcmyguest/openmem setup`, or `./setup` is a **meta** wizard: it
13
+ orchestrates **machine-local plumbing** that is the same idea across providers (Docker project,
14
+ ports, indexer sources, smoke “is the API alive?”, coding-agent config file, compose up/down). It does
15
+ **not** replace each provider’s **native** settings UI, CLI, or config format.
16
+
17
+ | This wizard owns (cross-cutting) | Provider still owns (native) |
18
+ | --- | --- |
19
+ | Pick provider + stack action (configure / up / down) | API keys, org/project ids, mem0 graph settings |
20
+ | Host ports, `HOST_UID`/`HOST_GID`, compose project name | Hindsight retain/reflect models beyond our LiteLLM routes |
21
+ | Shared `index.yml` (what to index) | Provider-specific memory schema, filters, user ids |
22
+ | Wiring Hindsight → LiteLLM proxy (when provider is Hindsight) | mem0 dashboard, cloud vs self-hosted choice |
23
+ | Health/smoke probes declared in manifest | Official provider docs, upgrades, migrations |
24
+
25
+ When a provider needs extra fields, the manifest should grow **provider-specific wizard steps** (or
26
+ point to “run `mem0 …` / edit `~/.mem0/…`”) — not cram mem0 into Hindsight’s LiteLLM screens.
27
+
28
+ ### Similar patterns elsewhere
29
+
30
+ These are the usual analogues people mean by “meta setup”:
31
+
32
+ - **[mise](https://mise.jdx.dev/) / [asdf](https://asdf-vm.com/)** — one CLI to install and select
33
+ runtimes; each plugin wraps the tool’s own install rules. mise does not replace `.npmrc` or
34
+ `Cargo.toml`.
35
+ - **[Chezmoi](https://www.chezmoi.io/)** — applies dotfiles from a repo; Vim, Git, and SSH keep their
36
+ normal config files on disk.
37
+ - **[Supabase CLI](https://supabase.com/docs/guides/cli)** — `supabase start` brings up Postgres,
38
+ Auth, Studio; each service still has its own env and tuning.
39
+ - **LiteLLM (in this stack)** — meta proxy in front of lemond/Ollama; model servers keep their own
40
+ configs. `./setup`’s “model backend” step is really “how LiteLLM reaches your LLM,” not a full
41
+ lemond replacement.
42
+ - **[Home Assistant](https://www.home-assistant.io/)** — core + integrations; Philips Hue still configured
43
+ in the Hue app, HA only links it.
44
+ - **`npx skills`-style installers** — discover and install artifacts; each skill or package owns its
45
+ content afterward.
46
+
47
+ openmem sits in that category: **one front door** for “memory on this machine for coding agents,”
48
+ not a reimplementation of mem0 or Hindsight settings.
49
+
50
+ Declarative config is **[mise-like](openmem-config.md)**: `~/.config/personal-memory/config.toml`
51
+ plus `./pm` tasks — same *shape* as mise (`[tasks]`, merged config files), different domain.
52
+
53
+ Provider-specific setup runs via **[wizard hooks](provider-wizard-hooks.md)** (`providers/*/wizard.mjs`)
54
+ after meta steps — like asdf plugin install scripts, not reimplemented npm/mem0 CLIs.
55
+
56
+ ## Layers (what varies per provider)
57
+
58
+ | Layer | Hindsight today | Should become |
59
+ | --- | --- | --- |
60
+ | **Compose stack** | `litellm`, `hindsight`, `indexer`, optional `notebooklm` | Provider manifest lists services / profiles; shared `indexer` where possible |
61
+ | **Model routing** | LiteLLM + `config/litellm.*.yaml` | Hindsight-specific; other providers may use their own LLM config or none |
62
+ | **Indexer sink** | `bin/hindsight-project-index` → REST `/v1/default/banks/{bank}/memories` | Python **sink** interface; one implementation per provider |
63
+ | **Setup wizard** | Node wizard: preflight, env, smoke retain/recall | Provider manifest: health URL, smoke steps, env file names |
64
+ | **Coding-agent UX** | `@vectorize-io/hindsight-coding-agents` + `coding-agent.json` | Per-provider plugin package or generic adapter |
65
+
66
+ NotebookLM sync is **orthogonal** to which provider you picked: enable the `notebooklm` profile and the sync container uses the same `MEMORY_SINK` as the indexer (Hindsight, mem0, or Cognee).
67
+
68
+ Coding-agent session memory uses the **same** in-repo hooks and unified MCP for every provider; on Hindsight stacks `--provider-plugin` swaps the in-repo hooks for the upstream Hindsight plugin (`[agents] integration = "provider"`).
69
+
70
+ ## Provider manifest
71
+
72
+ Each provider lives under `providers/<id>/manifest.json`. The wizard loads manifests via
73
+ `wizard/lib/providers/registry.mjs`. Fields are intentionally small; expand only when a second
74
+ provider needs them.
75
+
76
+ - **`status`**: `supported` | `experimental` | `planned`
77
+ - **`compose`**: which services belong to this provider, health check, default host port env var
78
+ - **`indexer`**: env var for API base URL, `sink`, and `sinkDriver` (see the table below)
79
+ - **`codingAgent`**: optional npx package and config template path
80
+ - **`wizard`**: smoke test bank name and HTTP paths (templates may include `{bank}`)
81
+
82
+ | `indexer` field | Meaning | Required |
83
+ | --- | --- | --- |
84
+ | `apiUrlEnv` | Env var that holds the provider's API base URL for the wizard | Per provider |
85
+ | `sink` | The `MEMORY_SINK` value, which equals the `indexer/sinks/<sink>.py` module name. Must match `^[a-z][a-z0-9_]*$` (the same rule as `indexer/sinks/__init__.py`). | `supported` and `experimental`; optional but validated for `planned` |
86
+ | `sinkDriver` | Human-readable wire-dialect label (for example `hindsight-rest-v1`). It names no module and drives no dispatch. | `supported` and `experimental` |
87
+
88
+ `scripts/tests/test_provider_manifests.mjs` checks that every `supported` or `experimental` manifest
89
+ with an `indexer.sink` has a matching `indexer/sinks/<sink>.py`.
90
+
91
+ ## Selecting a sink
92
+
93
+ The indexer picks its provider with `--sink NAME`, falling back to the `MEMORY_SINK` environment
94
+ variable, then `hindsight`. Compose fragments for alternate providers set both `MEMORY_SINK` and
95
+ `MEMORY_API_URL` on the `indexer` service from stack `.env` values.
96
+
97
+ | Variable | Used by | Meaning |
98
+ | --- | --- | --- |
99
+ | `MEMORY_SINK` | indexer | Sink module name (`indexer.sink` in the manifest). Default `hindsight`. |
100
+ | `MEMORY_API_URL` | indexer, every non-Hindsight sink | Generic API base URL of the provider. |
101
+ | `HINDSIGHT_URL` | indexer, Hindsight sink only | API base URL for Hindsight (default `http://127.0.0.1:8888`). |
102
+
103
+ `--url` overrides either URL variable. A sink reads its credentials from its own environment
104
+ variables inside `create`, never from the document path.
105
+
106
+ Provider secrets (for example `MEM0_API_KEY`, `COGNEE_API_KEY`, `LLM_API_KEY`) live in
107
+ `~/.config/personal-memory/<id>.env`, outside the repo, mode 600. A provider's compose fragment loads
108
+ that file through an `env_file` entry with `required: false`, so a stack without the file still
109
+ starts. Secrets do not belong in the stack `.env` or in `wizard.conf`, and the wizard never prints
110
+ them.
111
+
112
+ ### Optional `flush()`
113
+
114
+ A sink for a provider that ingests asynchronously (`capabilities.async_ingest=True`, for example
115
+ Cognee, where `cognify` builds the graph after `add`) may define `flush(bank: str) -> None`. The
116
+ indexer calls it once per bank after that bank's batch of retains and deletes, so the provider starts
117
+ processing once per batch rather than once per document.
118
+
119
+ - The call happens only with `--apply`. The indexer calls `flush(bank)` with an explicit bank when the
120
+ batch retained or deleted at least one document for that bank, **or** when a previous flush for that
121
+ bank is still pending. A dry run never flushes, and `flush(None)` is never called.
122
+ - A sink must treat `flush(bank)` as "process this bank now", even when its own in-memory bookkeeping
123
+ says the bank is clean: the indexer may call it from a fresh process in which that bookkeeping is gone.
124
+ - Pending flushes are persisted. Per-document progress is saved to the indexer state file as it goes, so
125
+ a failed flush (or a run that died after some retains) would otherwise never be retried. For a sink
126
+ with a `flush`, the bank is recorded under the state key `::flush-pending` (a sorted list of banks)
127
+ before the first write of a batch and removed after `flush` succeeds. Every `--apply` run flushes the
128
+ banks it handles that are still pending, even when nothing changed. A bank whose source is no longer
129
+ indexed is left alone. Sinks without `flush` (Hindsight) never get the key, so their state file is
130
+ unchanged.
131
+ - `flush` is not part of the `Sink` protocol, so sinks without it (Hindsight among them) still satisfy
132
+ `isinstance(sink, Sink)` and see no change in behavior. Callers use `sinks.flush_sink(sink, bank)`,
133
+ which does nothing when the sink has no callable `flush`.
134
+ - A flush error is handled like a retain error: the source is reported as failed, the other sources
135
+ still run, and the pending marker stays so the next run flushes again.
136
+ - The watcher (`indexer/watch.py`) spawns this same CLI once per source, so flush runs there too. In
137
+ the indexer container the sink and URL come from `MEMORY_SINK` and `MEMORY_API_URL`.
138
+
139
+ Default provider id: **`hindsight`**. Change it in **interactive `./setup`** (Clack “Memory provider”,
140
+ defaults to `config.toml` and `wizard.conf`), or set `MEMORY_PROVIDER` for `--yes`. The wizard records
141
+ the choice in `wizard.conf`. Stack **up** / **down** are wizard actions (`--up`, `--down`, or Clack
142
+ first step); default action is full **configure and start**.
143
+
144
+ ### Changing provider later
145
+
146
+ Prefer **`npx @bcmyguest/openmem switch <id> --yes`** (checkout: `node cli/openmem.mjs switch <id> --yes`) over a full
147
+ interactive setup re-run when you only need to change backends:
148
+
149
+ - Updates `MEMORY_PROVIDER` in `~/.config/hindsight/wizard.conf`
150
+ - Updates `[memory].provider` in `~/.config/personal-memory/config.toml` when that file exists
151
+ - Unless `--no-stack`: compose down + up for the new provider (with mem0/Cognee readiness checks)
152
+ - Unless `--no-mcp`: runs `install-mcp --provider-only` so Cursor MCP points at the new provider
153
+
154
+ Switching does **not** migrate memories between backends. The NotebookLM compose profile and
155
+ `notebooklm` MCP entry are left alone. Flags and examples:
156
+ [README — Switch memory provider](../README.md#switch-memory-provider).
157
+
158
+ ## Suggested implementation phases (separate commits)
159
+
160
+ ### Phase A — Registry and docs (this commitset)
161
+
162
+ - Manifests for `hindsight`, `mem0`, and `cognee` (`supported`).
163
+ - Wizard reads manifest for health/smoke URLs; behavior unchanged for Hindsight.
164
+ - Document extension points (this file).
165
+
166
+ ### Phase B — Indexer sink interface (implemented)
167
+
168
+ - HTTP retain/delete/health live in `indexer/sinks/`; default sink is `hindsight`.
169
+ - CLI: `--sink <name>` or env `MEMORY_SINK`; non-Hindsight sinks use `MEMORY_API_URL` for the API base.
170
+ - Contract tests: `indexer/tests/sink_contract.py` and `test_sinks.py`. Per-provider bank mapping and compose env: [indexer-sinks.md](indexer-sinks.md).
171
+
172
+ ### Phase D — Compose profiles (implemented for Hindsight)
173
+
174
+ - `litellm` and `hindsight` use compose profile `hindsight`; the indexer always starts.
175
+ - `./setup` passes `--profile hindsight` on compose commands; `scripts/setup.mjs` writes `COMPOSE_PROFILES=hindsight` into the stack `.env`.
176
+ - Alternate providers layer `providers/<id>/compose.yaml` with their own profile (`MEMORY_SINK`, `MEMORY_API_URL`, `depends_on`).
177
+
178
+ ### Phase C — mem0 and Cognee (Wave 2 + Wave 3 DX)
179
+
180
+ - mem0 is **URL-only** in v1: the indexer talks to a mem0 server the operator already runs, and this
181
+ repo ships no mem0 container yet. A mem0 service built from a pinned upstream ref is a follow-up.
182
+ Operator runbook: `providers/mem0/OPERATOR.md`. Coding-agent: wizard `setupCodingAgent` prints
183
+ SDK/MCP steps (no fake `pluginPackage`); register Cursor MCP with
184
+ `npx @bcmyguest/openmem install-mcp --yes` / `npx @bcmyguest/openmem switch mem0 --yes`.
185
+ - Cognee runs in **docker**: a pinned `cognee/cognee` image on a loopback port with a named volume and
186
+ an optional env file (`~/.config/personal-memory/cognee.env`, mode 600).
187
+ Operator notes: `providers/cognee/OPERATOR.md`. Coding-agent: REST on the loopback port plus a
188
+ read-only stdio MCP (`cli/cognee-mcp.mjs`: `cognee_health` / `cognee_search`) registered by
189
+ `setupCodingAgent` / `npm run install-mcp` when `MEMORY_PROVIDER=cognee`.
190
+ - **Cognee + Ollama:** default endpoints use `host.docker.internal:11434` (host Ollama on the Docker
191
+ bridge). When this stack’s compose profile `ollama` publishes **loopback-only**
192
+ (`127.0.0.1:11434`), that default is refused from the Cognee container. Set
193
+ `PM_COGNEE_OLLAMA_COMPOSE=1` or `MEMORY_OLLAMA_IN_COMPOSE=1` (wizard Clack also detects
194
+ `MEMORY_OLLAMA_IN_COMPOSE=1` in `wizard.conf`) so defaults become `http://ollama:11434` and attach
195
+ Cognee to the same Docker network as the `ollama` service. Cognify still needs a model that can
196
+ emit Cognee’s structured extraction schemas; small local models (for example `gemma4:e2b`) may
197
+ pass HTTP add/cognify start but never finish the graph — treat as an operator/LLM tuning issue,
198
+ not a provider status gate.
199
+ - `hindsight`, `mem0`, and `cognee` manifests are **`supported`**; new backends still land as
200
+ `experimental` until wizard + indexer + smoke are wired.
201
+ - `indexer/sinks/mem0.py` and `indexer/sinks/cognee.py` implementing the sink protocol (Cognee also
202
+ implements the optional `flush()`).
203
+ - Wizard branch: provider select (Clack) before model backend when multiple providers are supported.
204
+ - Coding-agent story: mem0’s own SDK/plugin or a thin local proxy—document limitations.
205
+
206
+ ### Phase D — Compose profiles
207
+
208
+ - `docker compose --profile hindsight` vs `--profile mem0` generated or documented from manifests.
209
+ - Avoid duplicating indexer image; only swap `depends_on` and indexer `environment`.
210
+
211
+ ## mem0 notes (non-goals for Phase A)
212
+
213
+ mem0’s API and deployment model differ from Hindsight banks and retain/reflect aliases. Expect:
214
+
215
+ - Different **namespace** model instead of `bank` strings.
216
+ - Possibly **no** LiteLLM sidecar in this repo (mem0 cloud or separate compose).
217
+ - Coding-agent integration likely **not** the Hindsight npx package; manifest should name the real install path when known.
218
+
219
+ ## Developer deep dives (repo only)
220
+
221
+ These stay under `docs/` and are **not** synced to the Starlight site (see `site/scripts/lib/docs.mjs` —
222
+ only user-facing guides like [Architecture](architecture.md) are listed in `PAGES`).
223
+
224
+ - [Indexer sink drivers](indexer-sinks.md) — `sink` vs `sinkDriver`, env vars, bank/scope mapping for Hindsight, mem0, Cognee
225
+ - [MCP setup](mcp-setup.md) — unified MCP vs `--provider-only` vs NotebookLM MCP
226
+
227
+ ## Index sources
228
+
229
+ See [index-sources.md](index-sources.md), [sources-feeds.md](sources-feeds.md), [sources-zotero.md](sources-zotero.md), [sources-readwise.md](sources-readwise.md), and [index-sources-roadmap.md](index-sources-roadmap.md).
230
+
231
+ ## Coding-agent session hooks
232
+
233
+ See [coding-agent-hooks.md](coding-agent-hooks.md), [coding-agent-scopes.md](coding-agent-scopes.md), and `npx @bcmyguest/openmem install-coding-agent`.
234
+
235
+ ## Site and docs
236
+
237
+ Published pages sync from `docs/*.md` at build time. If you change mermaid in a synced doc (for example `docs/architecture.md`), run `cd site && pnpm render-diagrams` and commit `site/public/diagrams/*.svg`. Then run `SITE_URL=https://bcmyguest.github.io BASE_PATH=/openmem/ pnpm build` in `site/` — the same check as CI and GitHub Pages.
238
+
239
+ ## Adding a provider (checklist)
240
+
241
+ 1. Add `providers/<id>/manifest.json` with accurate `status`.
242
+ 2. Implement indexer sink (Phase B+).
243
+ 3. Add compose fragment or profile and document ports/env.
244
+ 4. Extend wizard: provider selection, provider-specific steps, smoke test.
245
+ 5. Document opt-in / agent config for that provider.
246
+ 6. Extend `scripts/tests/test_wizard.sh` or add provider-specific tests.
@@ -0,0 +1,198 @@
1
+ # Meridian route (Claude subscription)
2
+
3
+ Route the three Hindsight aliases through [Meridian](https://github.com/rynfar/meridian), a local proxy that serves a signed-in Claude subscription over the Anthropic Messages and OpenAI Chat Completions APIs. Hindsight still calls `http://litellm:4000/v1`; only the override YAML changes.
4
+
5
+ ```
6
+ hindsight ──openai──▶ litellm:4000 ──anthropic/──▶ host.docker.internal:3456 (Meridian) ──Agent SDK──▶ Claude subscription
7
+ ```
8
+
9
+ Checked against Meridian 1.87.0 docs and LiteLLM docs on 2026-10-29. Nothing below was run end to end; steps marked **unverified** rest on docs alone.
10
+
11
+ ## Prerequisites
12
+
13
+ | Need | Why |
14
+ | --- | --- |
15
+ | Node.js 22+ | [Meridian quick start](https://github.com/rynfar/meridian#quick-start) |
16
+ | Claude Code CLI, signed in (`claude login`) | Meridian runs requests through the Agent SDK with those credentials. Haiku 5.5 needs Claude Code 2.1.293+ ([Haiku 5.5](https://github.com/rynfar/meridian/blob/main/docs/configuration.md#haiku-55)) |
17
+ | Claude Pro or Max plan | Requests count against plan limits |
18
+ | OpenMem stack set up | `node scripts/setup.mjs` has run once |
19
+
20
+ ## Run Meridian
21
+
22
+ ```sh
23
+ npm install -g @rynfar/meridian
24
+ claude login
25
+ ```
26
+
27
+ Meridian binds `127.0.0.1:3456` by default. The LiteLLM container reaches the host through `host.docker.internal` (`extra_hosts: host-gateway` in `compose.yaml`), which resolves to the Docker bridge address, not loopback, so a loopback-only Meridian is unreachable from the container on Linux. Bind all interfaces and require a key:
28
+
29
+ | Variable | Value | Source |
30
+ | --- | --- | --- |
31
+ | `MERIDIAN_HOST` | `0.0.0.0` (default `127.0.0.1`) | [Configuration table](https://github.com/rynfar/meridian/blob/main/docs/configuration.md#configuration) |
32
+ | `MERIDIAN_PORT` | `3456` (default) | same |
33
+ | `MERIDIAN_API_KEY` | long random secret. Clients send it as `x-api-key` or `Authorization: Bearer`; `/` and `/health` stay open | [API Key Authentication](https://github.com/rynfar/meridian/blob/main/docs/configuration.md#api-key-authentication) |
34
+
35
+ `0.0.0.0` also exposes port 3456 on every host interface; firewall it to the Docker bridge if the machine sits on a shared network.
36
+
37
+ ```sh
38
+ install -d -m 700 ~/.config/meridian
39
+ printf 'MERIDIAN_HOST=0.0.0.0\nMERIDIAN_API_KEY=%s\n' "$(openssl rand -hex 32)" > ~/.config/meridian/meridian.env
40
+ chmod 600 ~/.config/meridian/meridian.env
41
+ set -a; . ~/.config/meridian/meridian.env; set +a
42
+ meridian
43
+ ```
44
+
45
+ ### Keep it running
46
+
47
+ Meridian documents a Nix Home Manager user service and a [systemd socket-activation example](https://github.com/rynfar/meridian/blob/main/docs/configuration.md#systemd-socket-activation). For an npm install it ships `bin/meridian-launchd.sh`, a POSIX launcher it documents as usable "as a systemd `ExecStart`" ([Running as a service without drift](https://github.com/rynfar/meridian/blob/main/docs/configuration.md#running-as-a-service-without-drift)); the launcher self-updates at start unless `MERIDIAN_NO_SELF_UPDATE=1`.
48
+
49
+ The unit below is **unverified** (written from those docs, not tested). Replace `ExecStart` with the output of `echo "$(npm root -g)/@rynfar/meridian/bin/meridian-launchd.sh"`, and set `PATH` so the unit finds the same `node` and `npm` as your shell (nvm and volta installs live outside the systemd default `PATH`).
50
+
51
+ ```ini
52
+ # ~/.config/systemd/user/meridian.service
53
+ [Unit]
54
+ Description=Meridian proxy (Claude subscription)
55
+
56
+ [Service]
57
+ EnvironmentFile=%h/.config/meridian/meridian.env
58
+ Environment=PATH=/usr/local/bin:/usr/bin:/bin
59
+ ExecStart=/usr/lib/node_modules/@rynfar/meridian/bin/meridian-launchd.sh
60
+ Restart=on-failure
61
+
62
+ [Install]
63
+ WantedBy=default.target
64
+ ```
65
+
66
+ ```sh
67
+ systemctl --user daemon-reload
68
+ systemctl --user enable --now meridian
69
+ loginctl enable-linger "$USER" # keep it running while logged out
70
+ ```
71
+
72
+ **Docker alternative (unverified).** Meridian publishes no image; build it from the repo (`docker build -t meridian .`) and mount `~/.claude` ([Docker, single account](https://github.com/rynfar/meridian/blob/main/docs/deployment.md#single-account)). The image already sets `CLAUDE_PROXY_HOST=0.0.0.0`. Attach it to the stack network with `docker network connect openmem_default meridian` and use `api_base: http://meridian:3456`; nothing is then published to the host. Set `MERIDIAN_HOST_ID` and persist the volumes listed under [Persistence and host identity](https://github.com/rynfar/meridian/blob/main/docs/deployment.md#persistence-and-host-identity).
73
+
74
+ ## Provider choice: `anthropic/`
75
+
76
+ | | `anthropic/<model>` → `/v1/messages` | `openai/<model>` → `/v1/chat/completions` |
77
+ | --- | --- | --- |
78
+ | Meridian path | Primary API; Meridian's docs describe LiteLLM on this route (`passthrough` adapter, [Claude Code behind a gateway](https://github.com/rynfar/meridian/blob/main/docs/configuration.md#claude-code-behind-a-gateway)) | Supported; headerless tool loops get a derived session key ([OpenAI-compatible tools](https://github.com/rynfar/meridian/blob/main/docs/agents.md#openai-compatible-tools-open-webui-continue-etc)) |
79
+ | Tool calls (`memory-reflect`) | LiteLLM translates OpenAI `tools` to Anthropic `tool_use` ([function calling](https://docs.litellm.ai/docs/providers/anthropic#functiontool-calling)); Meridian returns calls to the client in passthrough mode ([Passthrough Mode](https://github.com/rynfar/meridian/blob/main/docs/configuration.md#passthrough-mode-and-tool-calling)) | Meridian translates OpenAI tools itself |
80
+ | `reasoning_effort` | LiteLLM maps it to `thinking` (budget 1024/2048/4096, or adaptive + `output_config.effort` on 4.6+ models) ([thinking](https://docs.litellm.ai/docs/providers/anthropic#usage---thinking--reasoning)). Drop it, below | Meridian's handling undocumented |
81
+ | `response_format` | LiteLLM converts it to native `output_format` on models it knows, else to a forced tool call ([structured outputs](https://docs.litellm.ai/docs/providers/anthropic#structured-outputs)). Meridian's handling of `output_format` is **unverified** | Meridian's handling undocumented |
82
+
83
+ Use `anthropic/`: it is the route Meridian documents for LiteLLM and gives the request shape LiteLLM translates most predictably. Fall back to `openai/<model>` with `api_base: http://host.docker.internal:3456/v1` if retain fails on structured output.
84
+
85
+ LiteLLM appends `/v1/messages` to `api_base`, so `api_base` has no `/v1` ([Custom API Base](https://docs.litellm.ai/docs/providers/anthropic#custom-api-base)). LiteLLM sends `api_key` as `x-api-key`, which Meridian accepts.
86
+
87
+ ## Thinking and token limits
88
+
89
+ The shipped `enable_thinking: false` is a llama.cpp chat-template flag; Claude ignores it. Remove it.
90
+
91
+ | Setting | Behavior through Meridian |
92
+ | --- | --- |
93
+ | Thinking off | Meridian refuses explicit `thinking: disabled` on Haiku 5.5 with HTTP 400 before generation ([Haiku 5.5](https://github.com/rynfar/meridian/blob/main/docs/configuration.md#haiku-55)). Send no thinking parameter; `additional_drop_params: ["reasoning_effort"]` stops LiteLLM generating one from Hindsight's `reasoning_effort` ([Specify params to drop](https://docs.litellm.ai/docs/completion/drop_params#specify-params-to-drop)) |
94
+ | `HINDSIGHT_API_*_MAX_COMPLETION_TOKENS`, LiteLLM `max_tokens` | Ignored by default: the Agent SDK has no output cap, so Meridian does not bound the answer ([Known limitations](https://github.com/rynfar/meridian/blob/main/docs/configuration.md#known-limitations)). Reflect tool calls are never truncated, and output is unbounded |
95
+ | `MERIDIAN_ENFORCE_MAX_TOKENS=1` | Enforces the cap on thinking plus text. With reflect at 2048, adaptive thinking can consume the budget, and a cap that lands mid tool call fails the request. Leave unset |
96
+ | `temperature`, `top_p` | Meridian's Claude docs do not say whether these reach the model; assume ignored (**unverified**). The README's "does not expose … sampling controls" sentence describes the Antigravity CLI backend, not Claude |
97
+
98
+ ## Route file
99
+
100
+ `~/.config/hindsight/litellm.yaml` replaces the backend route entirely. Model ids must appear in Meridian's `GET /v1/models`; `claude-haiku-5-5` is Meridian's `haiku` default and `claude-sonnet-5-5` appears in its client examples ([Pi](https://github.com/rynfar/meridian/blob/main/docs/agents.md#pi)). Reflect gets the larger model, as in the shipped routes.
101
+
102
+ ```yaml
103
+ # LiteLLM routes for Hindsight, backend: Meridian on the host (Claude subscription).
104
+ # See docs/meridian-litellm.md. MERIDIAN_API_KEY lives in ~/.config/hindsight/litellm.env.
105
+ model_list:
106
+ - model_name: memory-retain
107
+ litellm_params:
108
+ model: anthropic/claude-haiku-5-5
109
+ api_base: http://host.docker.internal:3456
110
+ api_key: os.environ/MERIDIAN_API_KEY
111
+ max_tokens: 4096
112
+ timeout: 300
113
+ additional_drop_params: ["reasoning_effort"]
114
+ - model_name: memory-reflect
115
+ litellm_params:
116
+ model: anthropic/claude-sonnet-5-5
117
+ api_base: http://host.docker.internal:3456
118
+ api_key: os.environ/MERIDIAN_API_KEY
119
+ max_tokens: 4096
120
+ timeout: 300
121
+ additional_drop_params: ["reasoning_effort"]
122
+ - model_name: memory-consolidate
123
+ litellm_params:
124
+ model: anthropic/claude-haiku-5-5
125
+ api_base: http://host.docker.internal:3456
126
+ api_key: os.environ/MERIDIAN_API_KEY
127
+ max_tokens: 4096
128
+ timeout: 300
129
+ additional_drop_params: ["reasoning_effort"]
130
+
131
+ general_settings:
132
+ master_key: os.environ/LITELLM_MASTER_KEY
133
+ ```
134
+
135
+ ## With the wizard
136
+
137
+ `./setup` step "3. Model backend" option 4, **Claude subscription (Meridian)**, automates [Route file](#route-file) and [Apply](#apply). It does not install or start Meridian; run it first as in [Run Meridian](#run-meridian).
138
+
139
+ | Step | What the wizard does |
140
+ | --- | --- |
141
+ | Ask | Meridian URL from the host (default `http://127.0.0.1:3456`, port from `MERIDIAN_PORT` in `~/.config/meridian/meridian.env`) and an optional `MERIDIAN_API_KEY` (blank reuses the environment, `meridian.env`, or `litellm.env`) |
142
+ | Probe | `GET /health` and `GET /v1/models` (with `x-api-key`) from the host, then `/health` on the `docker0` address. Answering on 127.0.0.1 but not on `docker0` means a loopback-only bind: it warns to set `MERIDIAN_HOST=0.0.0.0`. Without `docker0` it states the requirement |
143
+ | Models | Retain and consolidate: `claude-haiku-5-5`, reflect: `claude-sonnet-5-5`, when listed; otherwise the first listed Haiku and Sonnet ids. Unreachable Meridian: the guide defaults |
144
+ | Write | The route file above as `~/.config/hindsight/litellm.yaml` (an existing one is copied to `litellm.yaml.bak-<timestamp>`), `MERIDIAN_API_KEY` in `litellm.env` (mode 600; `placeholder` when Meridian runs without a key), and `[model] backend = "meridian"` in `wizard.conf` and `config.toml` |
145
+
146
+ Non-interactive: `PM_BACKEND=meridian MERIDIAN_BASE=http://127.0.0.1:3456 MERIDIAN_API_KEY=... ./setup --yes`. `[model.meridian] host` in `config.toml` sets `MERIDIAN_BASE`; `PM_DEFAULT_MODEL` and `PM_REFLECT_MODEL` pick models. `--dry-run` probes and prints the plan without writing. Then run the [Smoke test](#smoke-test); the wizard's host-side probe does not prove the container reaches Meridian through a firewall.
147
+
148
+ ## Apply
149
+
150
+ ```sh
151
+ cd ~/personal-memory # this checkout
152
+ [ -f ~/.config/hindsight/litellm.yaml ] && cp ~/.config/hindsight/litellm.yaml ~/.config/hindsight/litellm.yaml.bak-$(date +%Y%m%d%H%M%S)
153
+ $EDITOR ~/.config/hindsight/litellm.yaml # paste the route file
154
+ grep -h '^MERIDIAN_API_KEY' ~/.config/meridian/meridian.env >> ~/.config/hindsight/litellm.env
155
+ node scripts/setup.mjs
156
+ docker compose up -d --force-recreate litellm
157
+ ```
158
+
159
+ `--force-recreate` matters: Compose does not see edits inside the mounted YAML, so a plain `up -d` can keep the old routes. Revert by moving the override aside and rerunning the last two commands.
160
+
161
+ ## Smoke test
162
+
163
+ LiteLLM publishes no host port; run curl on the stack network (`openmem_default`, from `name: openmem`).
164
+
165
+ ```sh
166
+ set -a; . ~/.config/hindsight/litellm.env; set +a
167
+ net="--network openmem_default --add-host host.docker.internal:host-gateway"
168
+
169
+ # 1. Meridian reachable from a container, models listed
170
+ docker run --rm $net curlimages/curl -s http://host.docker.internal:3456/health
171
+ docker run --rm $net curlimages/curl -s -H "x-api-key: $MERIDIAN_API_KEY" http://host.docker.internal:3456/v1/models
172
+
173
+ # 2. Alias answers through LiteLLM
174
+ docker run --rm $net curlimages/curl -s http://litellm:4000/v1/chat/completions \
175
+ -H "Authorization: Bearer $LITELLM_MASTER_KEY" -H 'Content-Type: application/json' \
176
+ -d '{"model":"memory-retain","messages":[{"role":"user","content":"Reply with OK."}]}'
177
+
178
+ # 3. Reflect returns a tool call (expect finish_reason "tool_calls")
179
+ docker run --rm $net curlimages/curl -s http://litellm:4000/v1/chat/completions \
180
+ -H "Authorization: Bearer $LITELLM_MASTER_KEY" -H 'Content-Type: application/json' \
181
+ -d '{"model":"memory-reflect","reasoning_effort":"low","messages":[{"role":"user","content":"What is the weather in Paris? Use the tool."}],
182
+ "tools":[{"type":"function","function":{"name":"get_weather","description":"Weather for a city",
183
+ "parameters":{"type":"object","properties":{"city":{"type":"string"}},"required":["city"]}}}]}'
184
+ ```
185
+
186
+ Then run a smoke retain and reflect through Hindsight, as for any route change ([Model configuration](model-configuration.md#verify-and-migrate)).
187
+
188
+ ## Caveats
189
+
190
+ | Topic | Detail |
191
+ | --- | --- |
192
+ | Terms | Subscription use through third-party proxies is governed by Anthropic's [Consumer Terms](https://www.anthropic.com/legal/consumer-terms); see also Claude Code's [Authentication and credential use](https://code.claude.com/docs/en/legal-and-compliance#authentication-and-credential-use) |
193
+ | Third-party detection | Meridian reports Claude refusing Pi traffic as third-party ("Third-party apps now draw from your extra usage, not your plan limits") until Pi's prompt fingerprint is scrubbed ([Pi](https://github.com/rynfar/meridian/blob/main/docs/agents.md#pi)). Whether Hindsight's prompts trigger the same is **unverified**; watch Meridian's `/telemetry` |
194
+ | Plan limits | Retain runs on every ingest; indexing a large repo spends plan quota. `GET /v1/usage/quota` shows the windows |
195
+ | Concurrency | Meridian serializes requests that share a session identity and can return `400 This session advanced while the request was waiting` ([Concurrent requests](https://github.com/rynfar/meridian/blob/main/docs/configuration.md#concurrent-requests-to-the-same-session)). Keep `HINDSIGHT_API_LLM_MAX_CONCURRENT=1` (shipped default) |
196
+ | System prompt | Meridian can prepend Claude Code's own system prompt per adapter (SDK feature toggles at `/settings`). An [adapter instance](https://github.com/rynfar/meridian/blob/main/docs/agents.md#adapter-instances) matching `userAgentPrefix: "litellm/"` can turn it off; effect on Hindsight output is **unverified** |
197
+ | Exposure | `MERIDIAN_HOST=0.0.0.0` without `MERIDIAN_API_KEY` gives anyone on the network your subscription |
198
+ | Server tools | Anthropic server tools (`web_search_*`, `web_fetch_*`) return 400 through Meridian; Hindsight does not use them |
@@ -0,0 +1,106 @@
1
+ # Model configuration
2
+
3
+ Hindsight never names a model server. It calls the LiteLLM proxy at `http://litellm:4000/v1` with three fixed aliases, and the proxy's YAML maps each alias to a model:
4
+
5
+ | Alias | Hindsight operation |
6
+ | --- | --- |
7
+ | `memory-retain` | retain (extraction on ingest) |
8
+ | `memory-reflect` | reflect, the base `HINDSIGHT_API_LLM_MODEL`, and mental-model refresh. Needs a model that makes tool calls |
9
+ | `memory-consolidate` | consolidation |
10
+
11
+ ## Shipped routes
12
+
13
+ Edit the YAML (or a per-machine override) to change models.
14
+
15
+ | File | Backend | Default split |
16
+ | --- | --- | --- |
17
+ | `config/litellm.lemond.yaml` | lemond on the host | Reflect: Gemma 4 E4B GGUF (`unsloth/`); retain and consolidate: E2B |
18
+ | `config/litellm.ollama.yaml` | Ollama on the host | Used when `MEMORY_BACKEND=ollama`; `gemma4:e4b` needs Ollama newer than 0.20.3 |
19
+ | `~/.config/hindsight/litellm.yaml` (written by `./setup`, backend `meridian`) | Meridian on the host (Claude subscription) | Retain and consolidate: `claude-haiku-5-5`; reflect: `claude-sonnet-5-5`. See [Meridian route](meridian-litellm.md#with-the-wizard) |
20
+
21
+ **Why reflect uses a larger model:** E2B often returns no usable tool call on Hindsight reflect (`finish_reason=length`). The shipped lemond route uses E4B for `memory-reflect` only. Qwen3.5-4B works as an alternative if you change the route; pick any model that passes a direct tool-call check and a smoke reflect.
22
+
23
+ ## Route selection
24
+
25
+ | Priority | Path |
26
+ | --- | --- |
27
+ | 1 | `~/.config/hindsight/litellm.yaml` — full override, mounted read-only into the proxy |
28
+ | 2 | `config/litellm.$MEMORY_BACKEND.yaml` — `lemond` (default) or `ollama` |
29
+
30
+ `scripts/setup.mjs` writes `LITELLM_CONFIG` in the stack `.env`. After changing backend, override, or routes:
31
+
32
+ ```sh
33
+ node scripts/setup.mjs
34
+ docker compose up -d litellm
35
+ ```
36
+
37
+ Optional Ollama backend:
38
+
39
+ ```sh
40
+ MEMORY_BACKEND=ollama node scripts/setup.mjs
41
+ docker compose up -d litellm
42
+ ```
43
+
44
+ ## Thinking and token limits
45
+
46
+ Thinking is off on all three aliases in the route YAML (`enable_thinking: false` / `think: false`) so replies are not empty when Hindsight sends low `reasoning_effort`.
47
+
48
+ | Layer | Role |
49
+ | --- | --- |
50
+ | LiteLLM `litellm_params.max_tokens` | Default when the client omits `max_tokens` (4096 in shipped routes) |
51
+ | `HINDSIGHT_API_*_MAX_COMPLETION_TOKENS` in `hindsight.env` | Hard cap Hindsight enforces (retain 16384; reflect and consolidation 2048) |
52
+
53
+ Retain cap must exceed each retain strategy's chunk size (coding-agent strategies use 12000) or Hindsight returns `400 Invalid retain strategy`.
54
+
55
+ ## Retain extraction mode
56
+
57
+ OpenMem’s shipped `config/hindsight.env.example` sets:
58
+
59
+ ```env
60
+ HINDSIGHT_API_RETAIN_EXTRACTION_MODE=verbatim
61
+ ```
62
+
63
+ **verbatim** keeps each chunk’s text as the memory unit and still calls the retain LLM for entities, time, and location (cheaper than default `concise` fact rewriting). Alternatives: `concise` / `verbose` (LLM facts), `chunks` (no LLM on ingest; pass entities yourself if needed), or `HINDSIGHT_API_LLM_PROVIDER=none` (chunks-only; reflect disabled). Per-bank strategies can override this (the indexer already uses `chunks` for `project-index`).
64
+
65
+ ## Secrets
66
+
67
+ `~/.config/hindsight/litellm.env` (mode 600): `LITELLM_MASTER_KEY` (Hindsight uses this as `HINDSIGHT_API_LLM_API_KEY`), plus provider keys referenced as `os.environ/NAME` in the YAML.
68
+
69
+ Example hosted reflect model:
70
+
71
+ ```yaml
72
+ - model_name: memory-reflect
73
+ litellm_params:
74
+ model: anthropic/claude-haiku-4-5
75
+ api_key: os.environ/ANTHROPIC_API_KEY
76
+ ```
77
+
78
+ Hindsight always sends the alias names above; you do not edit model ids in `hindsight.env` for route changes.
79
+
80
+ ## mem0 through the same LiteLLM
81
+
82
+ The `litellm` service carries its own compose profile `litellm` next to `hindsight` and `notebooklm`, so Hindsight's stack renders and starts as before. mem0's wizard can enable that profile: LiteLLM then runs without Hindsight, published on `127.0.0.1:${MEM0_LITELLM_PORT:-4000}` only, from `~/.config/personal-memory/mem0-litellm.yaml`, with the same `litellm.env` and master key.
83
+
84
+ | Alias | mem0 use |
85
+ | --- | --- |
86
+ | `memory-llm` | mem0's LLM (fact extraction and memory updates) |
87
+ | `memory-embed` | mem0's embedder, only for backends with an embedding model (Ollama `nomic-embed-text`, an OpenAI-compatible server) |
88
+
89
+ Claude (Anthropic API key or Meridian) has no embedding model; with those backends mem0 keeps its own embedder. The mem0 config keys and their sources are in [mem0 — Models through the stack LiteLLM](mem0.md#models-through-the-stack-litellm-optional).
90
+
91
+ ## Verify and migrate
92
+
93
+ Health check from the compose network:
94
+
95
+ ```sh
96
+ docker compose exec litellm python3 -c "import urllib.request;print(urllib.request.urlopen('http://127.0.0.1:4000/health/liveliness').read())"
97
+ ```
98
+
99
+ Older `hindsight.env` files that pointed at lemond/Ollama directly:
100
+
101
+ ```sh
102
+ node scripts/setup.mjs --migrate-env
103
+ docker compose up -d
104
+ ```
105
+
106
+ Migration rewrites LLM lines to use `http://litellm:4000/v1` and the three aliases; other lines stay as they are.