@bcmyguest/openmem 0.6.3 → 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 (102) hide show
  1. package/{skills → .agents/skills}/openmem/SKILL.md +5 -0
  2. package/README.md +80 -76
  3. package/cli/coding-agent/capabilities.mjs +14 -3
  4. package/cli/coding-agent/context-store.mjs +81 -0
  5. package/cli/coding-agent/context-trim.mjs +897 -0
  6. package/cli/coding-agent/file-io.mjs +72 -0
  7. package/cli/coding-agent/hook-harnesses.mjs +2 -1
  8. package/cli/coding-agent/hook-runner.mjs +28 -4
  9. package/cli/coding-agent/install-access.mjs +43 -17
  10. package/cli/coding-agent/install-context-proxy.mjs +333 -0
  11. package/cli/coding-agent/install-rtk.mjs +60 -0
  12. package/cli/coding-agent/install-shim.mjs +119 -15
  13. package/cli/coding-agent/opencode-config.mjs +21 -5
  14. package/cli/coding-agent/opencode-plugin.mjs +39 -3
  15. package/cli/coding-agent/opencode2-plugin/index.js +299 -0
  16. package/cli/coding-agent/recall-frame.mjs +29 -0
  17. package/cli/coding-agent/remove-upstream-access.mjs +122 -0
  18. package/cli/coding-agent/runtime.mjs +2 -1
  19. package/cli/coding-agent/session-db.mjs +626 -0
  20. package/cli/coding-agent/session-facade.mjs +30 -6
  21. package/cli/coding-agent/toml-edit.mjs +161 -0
  22. package/cli/context-management.mjs +254 -0
  23. package/cli/feed-sync.mjs +18 -2
  24. package/cli/install-coding-agent.mjs +173 -40
  25. package/cli/mcp/context-sql-runner.mjs +58 -0
  26. package/cli/mcp/context-store.mjs +670 -0
  27. package/cli/mcp/knowledge.mjs +333 -0
  28. package/cli/mcp/memory-facade.mjs +99 -33
  29. package/cli/openmem-mcp.mjs +3 -0
  30. package/cli/openmem.mjs +18 -1
  31. package/cli/schedule.mjs +176 -0
  32. package/cli/stack.mjs +2 -0
  33. package/cli/switch-memory-provider.mjs +1 -1
  34. package/compose.yaml +67 -4
  35. package/config/hindsight.env.example +2 -0
  36. package/config/openmem.toml.example +64 -2
  37. package/docs/architecture.md +35 -9
  38. package/docs/coding-agent-hooks.md +26 -2
  39. package/docs/coding-agents.md +27 -7
  40. package/docs/compatibility.md +80 -0
  41. package/docs/concepts.md +30 -7
  42. package/docs/context-management.md +255 -0
  43. package/docs/context-proxy.md +115 -0
  44. package/docs/context-research.md +215 -0
  45. package/docs/context-store.md +277 -0
  46. package/docs/hindsight.md +5 -2
  47. package/docs/mcp-setup.md +5 -2
  48. package/docs/mem0.md +39 -1
  49. package/docs/memory-providers.md +2 -2
  50. package/docs/meridian-litellm.md +198 -0
  51. package/docs/model-configuration.md +22 -0
  52. package/docs/npx-install.md +6 -4
  53. package/docs/openmem-config.md +2 -2
  54. package/docs/quickstart.md +12 -6
  55. package/docs/setup-wizard.md +5 -4
  56. package/docs/sources-feeds.md +9 -22
  57. package/package.json +5 -4
  58. package/providers/cognee/OPERATOR.md +2 -2
  59. package/providers/hindsight/lib/clack.mjs +122 -20
  60. package/providers/hindsight/lib/coding-agent.mjs +1 -1
  61. package/providers/hindsight/lib/config.mjs +6 -0
  62. package/providers/hindsight/lib/env-setup.mjs +2 -1
  63. package/providers/hindsight/lib/litellm-routes.mjs +21 -12
  64. package/providers/hindsight/lib/meridian.mjs +307 -0
  65. package/providers/hindsight/lib/model-backend.mjs +38 -9
  66. package/providers/hindsight/lib/probe.mjs +21 -0
  67. package/providers/hindsight/lib/stack-env.mjs +5 -12
  68. package/providers/hindsight/lib/state.mjs +6 -1
  69. package/providers/hindsight/lib/summary.mjs +16 -2
  70. package/providers/hindsight/lib/validate.mjs +6 -0
  71. package/providers/hindsight/wizard.mjs +3 -2
  72. package/providers/mem0/OPERATOR.md +21 -2
  73. package/providers/mem0/compose.yaml +24 -2
  74. package/providers/mem0/lib/coding-agent.mjs +1 -1
  75. package/providers/mem0/lib/configure.mjs +66 -0
  76. package/providers/mem0/lib/litellm.mjs +457 -0
  77. package/providers/mem0/lib/stack-env.mjs +40 -1
  78. package/providers/mem0/lib/summary.mjs +30 -2
  79. package/providers/mem0/manifest.json +1 -1
  80. package/providers/mem0/wizard.mjs +15 -2
  81. package/proxy/config.yaml +33 -0
  82. package/proxy/openmem_context/__init__.py +6 -0
  83. package/proxy/openmem_context/adapters.py +504 -0
  84. package/proxy/openmem_context/compress.py +53 -0
  85. package/proxy/openmem_context/core.py +606 -0
  86. package/proxy/openmem_context/embed.py +212 -0
  87. package/proxy/openmem_context/recall.py +49 -0
  88. package/proxy/openmem_context/session.py +70 -0
  89. package/proxy/openmem_context/sessiondb.py +276 -0
  90. package/proxy/openmem_context/settings.py +134 -0
  91. package/proxy/openmem_context/store.py +129 -0
  92. package/proxy/openmem_context/tokens.py +123 -0
  93. package/proxy/openmem_context/ttl.py +78 -0
  94. package/proxy/openmem_context_hook.py +34 -0
  95. package/scripts/check-pack.mjs +2 -1
  96. package/scripts/setup.mjs +14 -1
  97. package/server.json +2 -2
  98. package/wizard/lib/context-step.mjs +140 -0
  99. package/wizard/lib/litellm-env.mjs +55 -0
  100. package/wizard/lib/pm-config.mjs +212 -4
  101. package/wizard/lib/prompts.mjs +28 -0
  102. package/wizard/lib/run-wizard.mjs +26 -31
@@ -0,0 +1,176 @@
1
+ #!/usr/bin/env node
2
+ /** Install a per-user, hourly feed-sync job using the host scheduler. */
3
+ import { spawnSync } from "node:child_process";
4
+ import fs from "node:fs";
5
+ import os from "node:os";
6
+ import path from "node:path";
7
+ import { parseArgs } from "node:util";
8
+ import { fileURLToPath } from "node:url";
9
+ import { pythonCandidates } from "./feed-sync.mjs";
10
+ import { isMainModule } from "./is-main.mjs";
11
+
12
+ const PACKAGE_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
13
+ const PACKAGE_NAME = JSON.parse(fs.readFileSync(path.join(PACKAGE_ROOT, "package.json"), "utf8")).name;
14
+ const JOB_NAME = "openmem-feed-sync";
15
+ const LAUNCHD_LABEL = "io.github.bcmyguest.openmem.feed-sync";
16
+ const WINDOWS_TASK = "OpenMemFeedSync";
17
+
18
+ function escapeXml(value) {
19
+ return value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;");
20
+ }
21
+
22
+ function systemdArg(value) {
23
+ const escaped = value.replaceAll("\\", "\\\\").replaceAll('"', '\\"').replaceAll("%", "%%");
24
+ return `"${escaped}"`;
25
+ }
26
+
27
+ export function schedulePaths(platform, home, env = process.env) {
28
+ if (platform === "linux") {
29
+ const config = path.isAbsolute(env.XDG_CONFIG_HOME || "") ? env.XDG_CONFIG_HOME : path.join(home, ".config");
30
+ return {
31
+ runner: path.join(config, "openmem", "feed-sync-runner.mjs"),
32
+ service: path.join(config, "systemd", "user", `${JOB_NAME}.service`),
33
+ timer: path.join(config, "systemd", "user", `${JOB_NAME}.timer`),
34
+ };
35
+ }
36
+ if (platform === "darwin") {
37
+ return {
38
+ runner: path.join(home, "Library", "Application Support", "OpenMem", "feed-sync-runner.mjs"),
39
+ plist: path.join(home, "Library", "LaunchAgents", `${LAUNCHD_LABEL}.plist`),
40
+ };
41
+ }
42
+ if (platform === "win32") {
43
+ const local = path.win32.isAbsolute(env.LOCALAPPDATA || "") ? env.LOCALAPPDATA : path.win32.join(home, "AppData", "Local");
44
+ return { runner: path.win32.join(local, "OpenMem", "feed-sync-runner.mjs") };
45
+ }
46
+ throw new Error(`native scheduling is unavailable on ${platform}; run ${PACKAGE_NAME} feed-sync with your system scheduler`);
47
+ }
48
+
49
+ export function renderRunner(nodePath, npmCliPath, pythonPath) {
50
+ return `// Generated by OpenMem schedule install. Reinstall after changing Node.js installations.\n` +
51
+ `import { spawnSync } from "node:child_process";\n` +
52
+ `import path from "node:path";\n` +
53
+ `const node = ${JSON.stringify(nodePath)};\n` +
54
+ `const npm = ${JSON.stringify(npmCliPath)};\n` +
55
+ `const python = ${JSON.stringify(pythonPath)};\n` +
56
+ `const env = { ...process.env };\n` +
57
+ `const pathKey = Object.keys(env).find((key) => key.toLowerCase() === "path") || "PATH";\n` +
58
+ `env[pathKey] = [path.dirname(node), path.dirname(python), env[pathKey] || ""].join(path.delimiter);\n` +
59
+ `env.PYTHON = python;\n` +
60
+ `const result = spawnSync(node, [npm, "exec", "--yes", "--package=${PACKAGE_NAME}", "--", "openmem", "feed-sync"], { env, stdio: "inherit" });\n` +
61
+ `if (result.error) console.error(result.error.message);\n` +
62
+ `process.exit(result.status ?? 1);\n`;
63
+ }
64
+
65
+ export function renderSystemdUnits(nodePath, runnerPath) {
66
+ return {
67
+ service: `[Unit]\nDescription=Refresh OpenMem feed cache\n\n[Service]\nType=oneshot\nExecStart=${systemdArg(nodePath)} ${systemdArg(runnerPath)}\n`,
68
+ timer: `[Unit]\nDescription=Refresh OpenMem feed cache hourly\n\n[Timer]\nOnCalendar=hourly\nPersistent=true\n\n[Install]\nWantedBy=timers.target\n`,
69
+ };
70
+ }
71
+
72
+ export function renderLaunchAgent(nodePath, runnerPath) {
73
+ return `<?xml version="1.0" encoding="UTF-8"?>\n` +
74
+ `<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">\n` +
75
+ `<plist version="1.0"><dict>\n` +
76
+ ` <key>Label</key><string>${LAUNCHD_LABEL}</string>\n` +
77
+ ` <key>ProgramArguments</key><array><string>${escapeXml(nodePath)}</string><string>${escapeXml(runnerPath)}</string></array>\n` +
78
+ ` <key>StartInterval</key><integer>3600</integer>\n` +
79
+ `</dict></plist>\n`;
80
+ }
81
+
82
+ export function windowsTaskArgs(nodePath, runnerPath) {
83
+ return ["/create", "/sc", "hourly", "/mo", "1", "/tn", WINDOWS_TASK, "/tr", `"${nodePath}" "${runnerPath}"`, "/f"];
84
+ }
85
+
86
+ function run(command, args, env, allowMissing = false) {
87
+ const result = spawnSync(command, args, { encoding: "utf8", env });
88
+ if (result.error) throw result.error;
89
+ if (result.status !== 0 && !allowMissing) throw new Error(`${command} ${args.join(" ")}: ${(result.stderr || result.stdout).trim()}`);
90
+ return result.status === 0;
91
+ }
92
+
93
+ function writeFile(file, content) {
94
+ fs.mkdirSync(path.dirname(file), { recursive: true });
95
+ fs.writeFileSync(file, content, { mode: 0o600 });
96
+ }
97
+
98
+ function resolvePython(platform, env) {
99
+ for (const [command, prefix] of pythonCandidates(platform, env)) {
100
+ const result = spawnSync(command, [...prefix, "-c", "import sys, yaml; print(sys.executable)"], { encoding: "utf8", env });
101
+ if (result.status === 0) return result.stdout.trim();
102
+ }
103
+ throw new Error("Python 3 with PyYAML is required for feed sync; install PyYAML or set PYTHON to a working interpreter");
104
+ }
105
+
106
+ function install(platform, home, env, dryRun) {
107
+ const files = schedulePaths(platform, home, env);
108
+ const npmCli = env.npm_execpath;
109
+ if (!npmCli || !fs.existsSync(npmCli)) throw new Error("run schedule install through npx so OpenMem can locate npm");
110
+ const nodePath = process.execPath;
111
+ if (dryRun) {
112
+ console.log(`Would write ${files.runner} and install an hourly ${JOB_NAME} job using ${nodePath} and ${npmCli}`);
113
+ return;
114
+ }
115
+ const runner = renderRunner(nodePath, npmCli, resolvePython(platform, env));
116
+ writeFile(files.runner, runner);
117
+ if (platform === "linux") {
118
+ const units = renderSystemdUnits(nodePath, files.runner);
119
+ writeFile(files.service, units.service);
120
+ writeFile(files.timer, units.timer);
121
+ run("systemctl", ["--user", "daemon-reload"], env);
122
+ run("systemctl", ["--user", "enable", "--now", `${JOB_NAME}.timer`], env);
123
+ } else if (platform === "darwin") {
124
+ const target = `gui/${process.getuid()}`;
125
+ if (fs.existsSync(files.plist)) run("launchctl", ["bootout", target, files.plist], env, true);
126
+ writeFile(files.plist, renderLaunchAgent(nodePath, files.runner));
127
+ run("launchctl", ["bootstrap", target, files.plist], env);
128
+ } else {
129
+ run("schtasks", windowsTaskArgs(nodePath, files.runner), env);
130
+ }
131
+ console.log(`Installed hourly ${JOB_NAME} job`);
132
+ }
133
+
134
+ function remove(platform, home, env, dryRun) {
135
+ const files = schedulePaths(platform, home, env);
136
+ if (dryRun) {
137
+ console.log(`Would remove ${JOB_NAME} job and ${files.runner}`);
138
+ return;
139
+ }
140
+ if (platform === "linux") {
141
+ run("systemctl", ["--user", "disable", "--now", `${JOB_NAME}.timer`], env, true);
142
+ fs.rmSync(files.service, { force: true });
143
+ fs.rmSync(files.timer, { force: true });
144
+ run("systemctl", ["--user", "daemon-reload"], env);
145
+ } else if (platform === "darwin") {
146
+ if (fs.existsSync(files.plist)) run("launchctl", ["bootout", `gui/${process.getuid()}`, files.plist], env, true);
147
+ fs.rmSync(files.plist, { force: true });
148
+ } else {
149
+ if (run("schtasks", ["/query", "/tn", WINDOWS_TASK], env, true)) {
150
+ run("schtasks", ["/delete", "/tn", WINDOWS_TASK, "/f"], env);
151
+ }
152
+ }
153
+ fs.rmSync(files.runner, { force: true });
154
+ console.log(`Removed ${JOB_NAME} job`);
155
+ }
156
+
157
+ export function main(argv = process.argv.slice(2), platform = process.platform, home = os.homedir(), env = process.env) {
158
+ const { values, positionals } = parseArgs({ args: argv, options: { "dry-run": { type: "boolean" }, help: { type: "boolean" } }, allowPositionals: true });
159
+ if (values.help || positionals.length === 0) {
160
+ console.log(`Usage: npx ${PACKAGE_NAME} schedule <install|remove> [--dry-run]\nInstalls an hourly per-user feed sync with the native OS scheduler.`);
161
+ return;
162
+ }
163
+ if (positionals.length !== 1) throw new Error("schedule accepts one action: install or remove");
164
+ if (positionals[0] === "install") install(platform, home, env, values["dry-run"]);
165
+ else if (positionals[0] === "remove") remove(platform, home, env, values["dry-run"]);
166
+ else throw new Error(`unknown schedule action: ${positionals[0]}`);
167
+ }
168
+
169
+ if (isMainModule(import.meta.url)) {
170
+ try {
171
+ main();
172
+ } catch (error) {
173
+ console.error(`openmem schedule: ${error.message}`);
174
+ process.exitCode = 1;
175
+ }
176
+ }
package/cli/stack.mjs CHANGED
@@ -47,6 +47,8 @@ export const STACK_ASSETS = [
47
47
  include:
48
48
  /^config\/(?:[^/]+\.example|index\.yml|notebooklm\.yml|litellm\.[^/]+\.yaml|coding-agent\.json)$/,
49
49
  },
50
+ // context-proxy mounts ./proxy (LiteLLM config and hook).
51
+ { tree: "proxy" },
50
52
  // Per-provider compose fragments (hooks and manifests stay in the read-only package).
51
53
  { tree: "providers", include: /^providers\/[^/]+\/compose[^/]*\.ya?ml$/ },
52
54
  // Deliberately absent: scripts/ and wizard/. They are code, so they run from the
@@ -4,7 +4,7 @@
4
4
  *
5
5
  * npx openmem switch <hindsight|mem0|cognee> [--yes] [--dry-run] [--no-stack] [--no-mcp]
6
6
  * openmem-switch-provider …
7
- * npx . switch <id> … / ./pm switch <id> …
7
+ * node cli/openmem.mjs switch <id> … / ./pm switch <id> …
8
8
  *
9
9
  * Does not migrate data between providers. No secrets in logs.
10
10
  */
package/compose.yaml CHANGED
@@ -4,7 +4,9 @@ name: openmem
4
4
  # Set HINDSIGHT_ENV_FILE to use an env file other than ~/.config/hindsight/hindsight.env.
5
5
  #
6
6
  # Provider selection is by compose profile:
7
- # - litellm and hindsight carry the profile "hindsight" (and "notebooklm", which needs them).
7
+ # - litellm and hindsight carry the profile "hindsight" (and "notebooklm", which needs them). litellm also carries
8
+ # its own profile "litellm", so a provider other than Hindsight can run the stack LiteLLM as its model gateway
9
+ # (mem0: providers/mem0/compose.yaml publishes it on loopback; docs/mem0.md "Models through the stack LiteLLM").
8
10
  # - indexer and indexer-state-init have no profile and no hard provider dependency; they start for every provider.
9
11
  # - Another provider ships providers/<id>/compose.yaml (see providers/_template/compose.yaml) with its own
10
12
  # profile, and layers over this file: docker compose -f compose.yaml -f providers/<id>/compose.yaml --profile <id> up -d
@@ -21,10 +23,11 @@ x-indexer-build: &indexer-build
21
23
  services:
22
24
  # OpenAI-compatible proxy in front of the model server. Hindsight calls it at http://litellm:4000/v1 with the
23
25
  # fixed aliases memory-retain, memory-reflect, memory-consolidate. Routes live in LITELLM_CONFIG (set by scripts/setup.sh).
24
- # Internal network only: nothing is published to the host.
26
+ # Internal network only under Hindsight: nothing is published to the host. The "litellm" profile runs it without
27
+ # Hindsight; providers/mem0/compose.yaml then publishes it on 127.0.0.1 and refuses to start without a master key.
25
28
  litellm:
26
- profiles: ["hindsight", "notebooklm"]
27
- image: ghcr.io/berriai/litellm:v1.105.1
29
+ profiles: ["hindsight", "notebooklm", "litellm"]
30
+ image: ghcr.io/berriai/litellm:v1.106.6
28
31
  command: ["--config", "/app/config.yaml", "--port", "4000"]
29
32
  env_file: ${LITELLM_ENV_FILE:-${HOME}/.config/hindsight/litellm.env}
30
33
  volumes:
@@ -157,6 +160,66 @@ services:
157
160
  required: false
158
161
  restart: unless-stopped
159
162
 
163
+ # Opt-in: docker compose --profile context-proxy up -d context-proxy. LiteLLM in front of Anthropic for coding
164
+ # agents: compresses large tool results and trims long sessions (docs/context-proxy.md). Separate from the litellm
165
+ # service and outside the hindsight profile, so Hindsight's own model traffic never passes through it.
166
+ context-proxy:
167
+ profiles: ["context-proxy"]
168
+ # Dev build on purpose: v1.109.0-dev.1 is the only LiteLLM release with PR #41522, the ChatGPT/Codex
169
+ # subscription pass-through (/backend-api/codex/{endpoint}) that routes Codex here; stable releases
170
+ # (v1.106.6, v1.108.0-rc.1) lack it. The litellm service above stays on stable. Move to the first stable
171
+ # release that contains #41522, then rerun proxy/tests/image/run.py.
172
+ image: ghcr.io/berriai/litellm:v1.109.0-dev.1
173
+ command: ["--config", "/app/proxy/config.yaml", "--port", "4000"]
174
+ # LITELLM_MASTER_KEY (and any provider keys for extra routes). Written by install-coding-agent.
175
+ env_file: ${CONTEXT_PROXY_ENV_FILE:-${HOME}/.config/personal-memory/context-proxy.env}
176
+ # Host UID/GID from .env, written by scripts/setup.sh; the context store stays host-owned (docs/context-store.md).
177
+ user: "${HOST_UID:?run scripts/setup.sh}:${HOST_GID:?run scripts/setup.sh}"
178
+ ports:
179
+ - "127.0.0.1:${CONTEXT_PROXY_PORT:-4100}:4000"
180
+ environment:
181
+ HOME: /tmp
182
+ PYTHONDONTWRITEBYTECODE: "1"
183
+ OPENMEM_STATE_DIR: /state
184
+ HINDSIGHT_URL: ${CONTEXT_PROXY_HINDSIGHT_URL:-http://hindsight:8888}
185
+ CONTEXT_PROXY_ENABLED: ${CONTEXT_PROXY_ENABLED:-1}
186
+ # From config.toml [context_proxy] trim_at_tokens / trim_to_fraction / compress_over_tokens / standing_instruction / chars_per_token, [compaction] max_chars / recall
187
+ # and [context_store] ttl_days / embedding_model / embedding_timeout_s / embedding_max_chars,
188
+ # written into .env by scripts/setup.mjs and install-coding-agent; a shell export overrides .env.
189
+ # CONTEXT_TRIM_AT_TOKENS stays empty when unset, so the proxy picks the threshold by model.
190
+ CONTEXT_TRIM_AT_TOKENS: ${CONTEXT_TRIM_AT_TOKENS:-}
191
+ CONTEXT_TRIM_TO_FRACTION: ${CONTEXT_TRIM_TO_FRACTION:-0.3}
192
+ CONTEXT_COMPRESS_OVER_TOKENS: ${CONTEXT_COMPRESS_OVER_TOKENS:-4000}
193
+ CONTEXT_COMPACTION_MAX_CHARS: ${CONTEXT_COMPACTION_MAX_CHARS:-12000}
194
+ CONTEXT_COMPACTION_RECALL: ${CONTEXT_COMPACTION_RECALL:-1}
195
+ CONTEXT_STANDING_INSTRUCTION: ${CONTEXT_STANDING_INSTRUCTION:-1}
196
+ CONTEXT_CHARS_PER_TOKEN: ${CONTEXT_CHARS_PER_TOKEN:-2.5}
197
+ # Session databases (docs/context-store.md): [context_store] ttl_days and embedding_model. Vectors are off while
198
+ # CONTEXT_EMBEDDING_MODEL is empty; the embeddings URL is the container's view of an OpenAI-compatible
199
+ # /embeddings endpoint (Ollama on the host by default). CONTEXT_EMBEDDING_API_KEY, if needed, goes in the env file.
200
+ CONTEXT_STORE_TTL_DAYS: ${CONTEXT_STORE_TTL_DAYS:-30}
201
+ CONTEXT_EMBEDDING_MODEL: ${CONTEXT_EMBEDDING_MODEL:-}
202
+ CONTEXT_EMBEDDING_URL: ${CONTEXT_PROXY_EMBEDDING_URL:-http://host.docker.internal:11434/v1}
203
+ CONTEXT_EMBEDDING_TIMEOUT_S: ${CONTEXT_EMBEDDING_TIMEOUT_S:-30}
204
+ CONTEXT_EMBEDDING_MAX_CHARS: ${CONTEXT_EMBEDDING_MAX_CHARS:-4000}
205
+ volumes:
206
+ - ./proxy:/app/proxy:ro
207
+ # Create it first (mode 700, host-owned); compose would otherwise create a root-owned directory.
208
+ - type: bind
209
+ source: ${OPENMEM_STATE_DIR:-${HOME}/.local/share/openmem}/context
210
+ target: /state/context
211
+ bind:
212
+ create_host_path: false
213
+ extra_hosts:
214
+ - "host.docker.internal:host-gateway"
215
+ restart: unless-stopped
216
+ healthcheck:
217
+ test: ["CMD", "python3", "-c", "import urllib.request; urllib.request.urlopen('http://127.0.0.1:4000/health/liveliness', timeout=5)"]
218
+ interval: 15s
219
+ timeout: 10s
220
+ retries: 10
221
+ start_period: 30s
222
+
160
223
  volumes:
161
224
  hindsight-data:
162
225
  indexer-state:
@@ -19,6 +19,8 @@ HINDSIGHT_API_LLM_MAX_CONCURRENT=1
19
19
  HINDSIGHT_API_LLM_TIMEOUT=300
20
20
  HINDSIGHT_API_LLM_MAX_RETRIES=1
21
21
 
22
+ # verbatim: store chunk text as-is; LLM only extracts entities/time/location (faster than concise).
23
+ HINDSIGHT_API_RETAIN_EXTRACTION_MODE=verbatim
22
24
  HINDSIGHT_API_RETAIN_MAX_COMPLETION_TOKENS=16384
23
25
  HINDSIGHT_API_REFLECT_MAX_COMPLETION_TOKENS=2048
24
26
  HINDSIGHT_API_CONSOLIDATION_MAX_COMPLETION_TOKENS=2048
@@ -24,9 +24,9 @@ control_plane_port = 9999
24
24
  [wizard]
25
25
  action = "configure" # configure | up | down
26
26
 
27
- # LLM routing for Hindsight (LiteLLM → lemond/Ollama/custom). Not lemond's own config.
27
+ # LLM routing for Hindsight (LiteLLM → lemond/Ollama/custom/Meridian). Not lemond's own config.
28
28
  [model]
29
- backend = "lemond" # lemond | ollama | custom
29
+ backend = "lemond" # lemond | ollama | custom | meridian (Claude subscription, docs/meridian-litellm.md)
30
30
  retain = "Gemma-4-E2B-it-GGUF"
31
31
  reflect = "" # empty = shipped memory-reflect route
32
32
  api_base = "" # custom backend only
@@ -37,6 +37,68 @@ host = "http://127.0.0.1:13305"
37
37
  [model.ollama]
38
38
  host = "http://127.0.0.1:11434"
39
39
 
40
+ # meridian backend only: Meridian's URL as seen from this host. The wizard does not run Meridian.
41
+ # [model.meridian]
42
+ # host = "http://127.0.0.1:3456"
43
+
44
+ # Coding-agent wiring, written by `install-coding-agent`.
45
+ # integration: "openmem" = OpenMem session hooks; "provider" = the provider's own plugin
46
+ # (Hindsight: @vectorize-io/hindsight-coding-agents). The two hook sets are exclusive.
47
+ [agents]
48
+ integration = "openmem"
49
+
50
+ # Local LiteLLM context proxy (`install-coding-agent --context-proxy`). When enabled,
51
+ # Claude Code's ANTHROPIC_BASE_URL points at http://127.0.0.1:<port>.
52
+ # trim_at_tokens / compress_over_tokens (and [compaction] max_chars) reach the proxy
53
+ # container through the stack .env (written by ./setup and install-coding-agent) and the
54
+ # OpenCode 2 plugin; the env vars CONTEXT_TRIM_AT_TOKENS, CONTEXT_COMPRESS_OVER_TOKENS,
55
+ # CONTEXT_TRIM_TO_FRACTION, CONTEXT_STANDING_INSTRUCTION, CONTEXT_CHARS_PER_TOKEN and CONTEXT_COMPACTION_MAX_CHARS
56
+ # override them.
57
+ [context_proxy]
58
+ # enabled = false # master switch; agents without a key below follow it (unset: opencode2 on, others off)
59
+ port = 4100
60
+ trim_at_tokens = 120000 # trim a session once its request exceeds this many tokens
61
+ trim_to_fraction = 0.3 # a trim keeps older in-turn exchanges while the request is under this fraction of trim_at_tokens (0 < x < 1)
62
+ compress_over_tokens = 4000 # compress tool results larger than this
63
+ standing_instruction = true # constant system-prompt note telling the agent to search trimmed turns
64
+ chars_per_token = 2.5 # token estimate before a session reports usage; reported usage calibrates it after
65
+
66
+ # Session databases (sessions/<sid>.db, docs/context-store.md): every message of a trimmed session.
67
+ # ttl_days: delete sessions idle longer than this, checked at proxy / MCP start at most once a day (0 = keep).
68
+ # embedding_model: OpenAI-compatible embedding model for hybrid smart_compact_search; empty = vectors off.
69
+ # The stack ships no embedding route; e.g. `ollama pull nomic-embed-text` and set it here.
70
+ # embedding_url: that endpoint as this host sees it (the proxy container uses CONTEXT_PROXY_EMBEDDING_URL).
71
+ # embedding_timeout_s: seconds per row of a background embeddings call (a batch of n rows gets n times this).
72
+ # Batches start at 4 rows, double while calls finish well inside the timeout and halve on a failure.
73
+ # embedding_max_chars: characters of each row's head that are embedded; the vector covers only that part.
74
+ # 4000 fits nomic-embed-text's 2048-token context; raise it only for a model with a longer one.
75
+ [context_store]
76
+ ttl_days = 30
77
+ embedding_model = ""
78
+ embedding_url = "http://127.0.0.1:11434/v1"
79
+ embedding_timeout_s = 30
80
+ embedding_max_chars = 4000
81
+
82
+ # Optional per-agent overrides (written by the wizard and `openmem context on|off --agents`).
83
+ # claude-code and opencode1 go through the context-proxy service, which install-coding-agent starts
84
+ # when one of them is on and stops when none is. opencode2 trims in its own plugin (no service;
85
+ # CONTEXT_PROXY_ENABLED overrides it); with neither its key nor `enabled` set it is on.
86
+ # codex is accepted but not routed yet.
87
+ # [context_proxy.agents]
88
+ # claude-code = true
89
+ # opencode1 = true
90
+ # codex = false
91
+ # opencode2 = true
92
+
93
+ # native (OpenCode 2 only): when OpenCode compacts a session, OpenMem's plugin supplies the
94
+ # smart_compact_search pointer + recall as the summary instead of a model call.
95
+ # false leaves compaction to OpenCode.
96
+ # max_chars: cap on the recall text placed after a trim or compaction pointer.
97
+ [compaction]
98
+ native = true
99
+ max_chars = 12000
100
+ # recall = true # provider recall in the trim pointer, framed as untrusted data (env CONTEXT_COMPACTION_RECALL)
101
+
40
102
  # Optional indexer defaults (wizard may still prompt interactively)
41
103
  # [[index.roots]]
42
104
  # path = "~"
@@ -1,6 +1,25 @@
1
1
  # Architecture
2
2
 
3
- openmem’s job is **not** to be the memory — it is to let you run **whichever memory system you choose** on one machine with shared indexing and setup. A **shared indexer** writes into the active **provider**; **coding agents** reach that API through unified `memory_*` MCP, in-repo **session hooks**, and optional native paths (Hindsight plugin, mem0/Cognee MCP). Only one provider is active at a time; `npx @bcmyguest/openmem switch` rewires the stack without pretending to migrate your data. Setup (`npx @bcmyguest/openmem`) is install plumbing only — not part of the runtime data path.
3
+ OpenMem connects coding agents and a shared indexer to one active [memory provider](provider-status.md). Agents use the stable `memory_*` MCP and session hooks; the indexer writes sources to the same provider. This page shows the full runtime paths and optional integrations. Setup (`npx @bcmyguest/openmem`) configures them but is outside the runtime data path. Switching providers does not migrate memories.
4
+
5
+ ## Overview
6
+
7
+ Two paths. **A, memory:** sources go through the indexer into one active provider, which agents reach through the `memory_*` MCP and session hooks. **B, context management:** the context proxy (OpenCode 2: a native plugin) keeps each session under `trim_at_tokens` by moving older turns to a local session store that the agent searches; no model call summarizes them. OpenMem parts are tinted.
8
+
9
+ <p align="center">
10
+ <picture>
11
+ <source media="(prefers-color-scheme: dark)" srcset="https://bcmyguest.github.io/openmem/media/overview-memory-dark.png" />
12
+ <img src="https://bcmyguest.github.io/openmem/media/overview-memory.png" alt="A, memory: sources, OpenMem indexer, one active provider, OpenMem MCP, coding agents with session hooks" width="360" />
13
+ </picture>
14
+ <picture>
15
+ <source media="(prefers-color-scheme: dark)" srcset="https://bcmyguest.github.io/openmem/media/overview-context-dark.png" />
16
+ <img src="https://bcmyguest.github.io/openmem/media/overview-context.png" alt="B, context management: coding agent, context proxy, model API, session store read back over MCP; OpenCode 2 uses a native plugin" width="360" />
17
+ </picture>
18
+ <br />
19
+ <sub>A, memory · B, context management</sub>
20
+ </p>
21
+
22
+ The sections below show every runtime path and optional integration.
4
23
 
5
24
  ## Runtime data flow
6
25
 
@@ -31,7 +50,7 @@ flowchart TB
31
50
  AG[Coding agents]
32
51
  UMCP["openmem MCP\n(memory_*)"]
33
52
  SHIM["Session hooks\n(all providers)"]
34
- PLG["Hindsight plugin\n(optional --hindsight-plugin)"]
53
+ PLG["Hindsight plugin\n(--provider-plugin, replaces hooks)"]
35
54
  PMCP["Provider MCP\nmem0 or Cognee, optional"]
36
55
  NLMCP["NotebookLM MCP\noptional, separate server"]
37
56
  end
@@ -68,7 +87,7 @@ Only **one provider** is active at a time (`MEMORY_PROVIDER`). The indexer uses
68
87
 
69
88
  Product framing: [Concepts](concepts.md).
70
89
 
71
- openmem is a **meta layer**: it does not replace provider APIs. It wires the same plumbing (indexer, compose, wizard, MCP) to **different backends** through manifests and thin adapters.
90
+ OpenMem connects the indexer, Compose, wizard, and MCP to the selected provider through its manifest and adapter. Provider support levels come from [Provider status](provider-status.md).
72
91
 
73
92
  ```mermaid
74
93
  flowchart TB
@@ -110,7 +129,7 @@ flowchart TB
110
129
  | **Wizard hooks** | Provider-specific env, keys, smoke, coding-agent notes | `providers/<id>/wizard.mjs` |
111
130
  | **Indexer sink** | Poll/vault → retain/update/delete on the provider API | `indexer/` (driver id in manifest, e.g. `hindsight-rest-v1`, `mem0-rest`) |
112
131
  | **Unified MCP** | Stable `memory_*` tools → provider HTTP | `cli/mcp/memory-facade.mjs`, `cli/openmem-mcp.mjs` |
113
- | **Native agent paths** | Session hooks + optional plugin/MCP | `cli/coding-agent/hook-runner.mjs`; optional Hindsight plugin; `cli/mem0-mcp.mjs`, `cli/cognee-mcp.mjs`, NotebookLM |
132
+ | **Native agent paths** | Session hooks or provider plugin (`[agents] integration`) + optional MCP | `cli/coding-agent/hook-runner.mjs`; optional Hindsight plugin; `cli/mem0-mcp.mjs`, `cli/cognee-mcp.mjs`, NotebookLM |
114
133
 
115
134
  Adding a provider means a new manifest + hooks + sink driver; agents can keep using **`memory_*`** once the facade implements that backend.
116
135
 
@@ -118,7 +137,7 @@ Adding a provider means a new manifest + hooks + sink driver; agents can keep us
118
137
 
119
138
  | Input | Resolves |
120
139
  | --- | --- |
121
- | `MEMORY_PROVIDER` | Active backend (`wizard.conf`, optional `config.toml`) |
140
+ | `MEMORY_PROVIDER` | Active provider (`wizard.conf`, optional `config.toml`) |
122
141
  | Stack `.env` | API URLs, ports, `MEMORY_SINK`, indexer → provider URL |
123
142
  | Provider secret files | e.g. `~/.config/personal-memory/mem0.env` (never committed; not written to `mcp.json`) |
124
143
  | `~/.config/hindsight/index.yml` | What the indexer watches |
@@ -153,8 +172,8 @@ Default scope key for MCP-created rows: bank / `user_id` **`openmem-mcp`** unles
153
172
 
154
173
  | Provider | Session / tools | Install |
155
174
  | --- | --- | --- |
156
- | **All providers** | In-repo session hooks → unified `memory_*` | `install-coding-agent --yes` |
157
- | **Hindsight (optional)** | `@vectorize-io/hindsight-coding-agents` plugin | `install-coding-agent --hindsight-plugin` |
175
+ | **All providers** | In-repo session hooks → unified `memory_*` (`[agents] integration = "openmem"`, default) | `install-coding-agent --yes` |
176
+ | **Hindsight (optional)** | `@vectorize-io/hindsight-coding-agents` plugin instead of the session hooks (`integration = "provider"`) | `install-coding-agent --provider-plugin` |
158
177
  | **mem0** | `@yeyuan98/mem0-mcp` tools | `install-mcp --provider-only` |
159
178
  | **Cognee** | `cognee_health`, `cognee_search` | `install-mcp --provider-only` |
160
179
  | **NotebookLM** | `notebooklm_list_banks`, `notebooklm_search` | `install-mcp` when sync enabled or `--notebooklm` |
@@ -194,15 +213,22 @@ There is **no** cross-vendor standard for agent memory APIs. MCP standardizes to
194
213
  | `memory_forget` | Delete by id (mem0) or drop a Hindsight bank |
195
214
  | `memory_provider_status` | Active provider and loopback URL (no secrets) |
196
215
  | `memory_export` / `memory_import` | PMM portable format — [Portable memory (PMM)](memory-portability.md) |
216
+ | `memory_project_scope` | Resolve a workspace's bank and hook access (local) |
217
+ | `memory_pages_list` / `memory_pages_search` / `memory_pages_read` | List, search, and read curated knowledge pages (Hindsight; others return `supported: false`) |
218
+ | `memory_capture_initiative` | Record an approved feature plan as a tracked initiative page plus marker memory (Hindsight) |
219
+ | `memory_status` | Async ingest drain state and operation status (Hindsight; mem0 always ready) |
220
+ | `smart_compact_search` | Search a session's SQLite store of every message (FTS5 BM25 `query`, merged with embeddings when present, or a `pattern` regex), filtered by `role` and `in_context`, with `context` neighbouring rows (local files, every provider) — [Context store](context-store.md) |
221
+ | `smart_compact_sql` | One read-only SELECT against a session database, for counts and aggregates (2 s, 200 rows) |
222
+ | `smart_compact_get` | Return the full text of a compressed tool result by blob id, paged at 100k chars (local files, every provider) |
197
223
 
198
- **Session hooks** (all providers) call the unified MCP via `cli/coding-agent/hook-runner.mjs`. Optional [`@vectorize-io/hindsight-coding-agents`](https://www.npmjs.com/package/@vectorize-io/hindsight-coding-agents) with `--hindsight-plugin` adds a second hook set on Hindsight stacks.
224
+ **Session hooks** (all providers) call the unified MCP via `cli/coding-agent/hook-runner.mjs`. Optional [`@vectorize-io/hindsight-coding-agents`](https://www.npmjs.com/package/@vectorize-io/hindsight-coding-agents) replaces them on Hindsight stacks: `--provider-plugin` sets `[agents] integration = "provider"`, removes OpenMem's hooks and keeps the unified MCP; `--no-provider-plugin` switches back. `--hindsight-plugin` was renamed to `--provider-plugin`. See [Coding-agent hooks](coding-agent-hooks.md).
199
225
 
200
226
  ### Other MCP integrations
201
227
 
202
228
  | Integration | Tools / path | Role |
203
229
  | --- | --- | --- |
204
230
  | Session hooks | SessionStart / prompt / Stop → `memory_*` | Default in-IDE session memory (all providers) |
205
- | Hindsight plugin (optional) | Upstream retain / recall / reflect | `--hindsight-plugin` only |
231
+ | Hindsight plugin (optional) | Upstream retain / recall / reflect, instead of the session hooks | `--provider-plugin` only |
206
232
  | `notebooklm` (`install-mcp`) | `notebooklm_list_banks`, `notebooklm_search` | Opt-in synced notebooks |
207
233
  | `mem0` (`--provider-only`) | mem0 REST tools via `@yeyuan98/mem0-mcp` | Native mem0 tool names |
208
234
  | `cognee` (`--provider-only`) | `cognee_health`, `cognee_search` | Native Cognee read-only MCP |
@@ -24,16 +24,40 @@ which session features work for the active provider.
24
24
  npx @bcmyguest/openmem install-coding-agent --yes
25
25
  ```
26
26
 
27
- - **All providers:** unified MCP and [shared skill](../skills/openmem/SKILL.md), plus session hooks (`cli/coding-agent/hook-runner.mjs`) for Claude Code, Codex, Cursor Agent, and a JavaScript OpenCode plugin (`cli/coding-agent/opencode-plugin.mjs`).
28
- - **Optional:** `--hindsight-plugin` with `MEMORY_PROVIDER=hindsight` also runs `@vectorize-io/hindsight-coding-agents install` (legacy/alternate hook set).
27
+ - **All providers:** unified MCP and [shared skill](../.agents/skills/openmem/SKILL.md), plus session hooks (`cli/coding-agent/hook-runner.mjs`) for Claude Code, Codex, Cursor Agent, and a JavaScript OpenCode plugin: `cli/coding-agent/opencode-plugin.mjs` for OpenCode 1, `cli/coding-agent/opencode2-plugin/` for OpenCode 2 (the installer picks one from `opencode --version`).
28
+ - **Exclusive hook sets:** `[agents] integration` in `~/.config/personal-memory/config.toml` picks one. `openmem` (default) installs the hooks above and removes the upstream Hindsight install: hook entries (commands under `hindsight-coding-agents` / `.hindsight/coding-agents`), the matching OpenCode `plugin` entry, the `hindsight` MCP server when it runs the upstream `dist/mcp-server.js` (Claude Code `~/.claude.json`, Codex `config.toml`, Cursor `mcp.json`), and the `hindsight-coding-agent` skill copies. Upstream writes no MCP or skill for OpenCode. `provider` (`--provider-plugin`, Hindsight only) removes OpenMem's hooks, keeps the unified MCP, and runs `@vectorize-io/hindsight-coding-agents install`. `--no-provider-plugin` switches back; `--hindsight-plugin` was renamed to `--provider-plugin`. Other providers always get OpenMem hooks.
29
+
30
+ SessionStart output starts with a two-line banner: provider, bank, and the tools to use. It names `memory_pages_search` and `memory_capture_initiative` only when `PROVIDER_CAPABILITIES` marks `knowledge_pages` / `initiatives` for the provider. The same hook writes `sessions/<sid>.json` per [context store](context-store.md); a write failure is ignored. Denied workspaces get no registration.
31
+
32
+ `/smart-compact`: the installer writes `~/.claude/commands/smart-compact.md`. The prompt hook (Claude Code, Codex, Cursor) and the OpenCode plugin write `sessions/<sid>.force` when the prompt starts with `/smart-compact`, contains `<command-name>/smart-compact</command-name>`, or contains the command body's `openmem:smart-compact` marker; that prompt skips recall. The flag is written even when memory hooks are denied. Claude Code's hook docs say UserPromptSubmit receives the typed `/command args` text; the other two forms cover expanded deliveries.
33
+
34
+ Cursor hooks live under `hooks` in `~/.cursor/hooks.json` with `version: 1`, as Cursor documents; reinstall moves entries older OpenMem versions wrote at the top level.
29
35
 
30
36
  See [MCP setup](mcp-setup.md) and [coding agents](coding-agents.md).
31
37
 
32
38
  Claude Code Stop retention parses ordinary message and tool-use rows. Codex Stop retention is best effort because Codex does not guarantee a stable transcript format. Cursor Stop retention has a bounded adapter for the observed local agent-transcripts JSONL shape (top-level user/assistant role and `message.content` parts). Live Cursor Hook `transcript_path` linkage to that format remains unverified; unknown row shapes are skipped. Cursor recall and explicit `memory_add` remain available. OpenCode retention uses its plugin's session messages API.
33
39
 
40
+ ### OpenCode 2 plugin
41
+
42
+ OpenCode 2 does not run OpenCode 1 plugins ([migration guide](https://opencode.ai/v2/docs/build/plugins/migrate-v1/)). `cli/coding-agent/opencode2-plugin/index.js` default-exports a plain `{ id: "openmem", setup }` object. `Plugin.define` from `@opencode/plugin` 2.2.1 returns its argument unchanged, and OpenCode reads the default export's `id` and `setup()` ([Support V1](https://opencode.ai/v2/docs/build/plugins/server/#support-v1)), so the plugin adds no runtime dependency. `setup(ctx)` registers:
43
+
44
+ | Feature | OpenCode 2 API | Behaviour |
45
+ | --- | --- | --- |
46
+ | Registration | `prompt` / `context` hooks, `session.created` event | Writes `sessions/<sid>.json` on the first hook call per session. In OpenCode 2.2.1 a location's plugins loaded after `opencode run` had created the session, so `session.created` never arrived |
47
+ | Prompt recall | `ctx.session.hook("prompt")` | First prompt: `memory_reflect`; later prompts: `memory_recall` |
48
+ | System context | `ctx.session.hook("context")` | Pushes the session banner onto `event.system`; puts `Personal memory for <bank>:` and the framed recall block in a leading text part of the user message the prompt belongs to, never in `event.system` |
49
+ | Native trim | `ctx.session.hook("context")` | Replaces `event.messages` per [context proxy](context-proxy.md) rules (`cli/coding-agent/context-trim.mjs`) |
50
+ | Native compaction | `ctx.session.hook("compaction")` | Sets `event.result.summary` to the pointer + recall; `[compaction] native = false` disables it |
51
+ | `/smart-compact` | `prompt` hook; `ctx.command.transform` adds a `smart-compact` command | Writes `sessions/<sid>.force`; no recall for that prompt |
52
+ | Retention | `ctx.session.hook("experimental.idle")`; `session.execution.succeeded`, `session.status` idle and `session.idle` events as fallbacks | Reads `ctx.session.context()` and retains the new user/assistant text once per turn |
53
+
54
+ The access policy matches OpenCode 1: denied workspaces get no registration, recall or retention, but `/smart-compact`, trimming and compression still run. As with the proxy, a trim pointer carries recall only when the session's registration names a bank.
55
+
34
56
  Hindsight Stop hooks submit retains with `async: true` so model extraction runs after the hook returns. A successful queue response advances the transcript cursor; later extraction failures must be checked through Hindsight operations. MCP `memory_add` defaults to async; pass `async: false` to wait for extraction.
35
57
  SessionStart hooks use recall instead of reflect. Hindsight startup recall requests `budget: low` and `max_tokens: 1200`; all hook recall output is limited to eight hits and 8000 characters. Explicit `memory_reflect` remains available through the MCP.
36
58
 
59
+ Every hook and plugin recall (SessionStart and prompt hooks for Claude Code, Codex and Cursor; the OpenCode 1 and 2 per-prompt recall) is framed as untrusted data with the trim pointer's frame ([format](context-store.md#proxy-inserted-text)): the fixed sentence, then the text in an `<openmem-recall untrusted="true">` block whose inner `openmem-recall` tags are escaped. The 8000-character cap includes the 147-character frame; the SessionStart banner and the OpenCode `Personal memory for <bank>:` label sit outside it. `hookRecallBlock` in `cli/coding-agent/session-facade.mjs` builds it from the shared `recallBlock` (`cli/coding-agent/recall-frame.mjs`). `[compaction] recall` does not affect hook recall.
60
+
37
61
  ## Session scopes
38
62
 
39
63
  Indexer banks map to provider scopes (`coding-agent::repo`, `obsidian::vault`, mem0 `user_id`, Cognee dataset names). Default MCP scope key: `openmem-mcp`. See [coding-agent-scopes.md](coding-agent-scopes.md).
@@ -1,22 +1,42 @@
1
1
  # Coding agents and MCP
2
2
 
3
+ Prerequisite: run the wizard (`npx @bcmyguest/openmem`) so a provider stack is up. The MCP server and hooks only forward to that stack. Per-agent support and evidence levels: [Compatibility matrix](compatibility.md).
4
+
3
5
  One entry point for session wiring on **every** provider (unified MCP + in-repo session hooks):
4
6
 
5
7
  ```sh
6
8
  npx @bcmyguest/openmem install-coding-agent --yes
7
- # optional: --hindsight-plugin (also run @vectorize-io/hindsight-coding-agents on Hindsight stacks)
9
+ # --provider-plugin / --no-provider-plugin: upstream Hindsight hooks instead of openmem's (saved in config)
10
+ # --no-rtk: skip rtk wiring; --context-proxy / --no-context-proxy: local context proxy (saved in config)
8
11
  # same flags: --dry-run, --agents claude-code,codex,opencode,cursor-cli,hermes,gemini-cli,amp,cline
9
12
  ```
10
13
 
11
- This stages a persistent runtime under `~/.local/share/personal-memory/agent-runtime`, registers the unified MCP globally in Claude Code, Codex, Cursor Agent, and OpenCode, and installs their session hooks. It also copies the [openmem skill](../skills/openmem/SKILL.md) into each agent's global skill directory. The runtime path stays valid after an `npx` cache cleanup. Re-run the command after upgrading this package to refresh the runtime and skill. See [hook parity](coding-agent-hooks.md).
14
+ Persisted choices live in `~/.config/personal-memory/config.toml`:
15
+
16
+ | Table | Key | Default | Flag |
17
+ | --- | --- | --- | --- |
18
+ | `[agents]` | `integration` = `openmem` \| `provider` | `openmem` | `--provider-plugin`, `--no-provider-plugin` |
19
+ | `[context_proxy]` | `enabled` | `false` | `--context-proxy`, `--no-context-proxy` |
20
+ | `[context_proxy]` | `port` | `4100` | edit the file |
21
+ | `[compaction]` | `native` | `true` | edit the file; OpenCode 2 only (see below) |
22
+
23
+ The two hook sets are exclusive; see [hook parity](coding-agent-hooks.md#install-per-agent).
24
+
25
+ **rtk.** When [`rtk`](https://github.com/rtk-ai/rtk) is on `PATH`, the installer runs rtk's own setup per selected agent: `rtk init -g --auto-patch` (Claude Code), `rtk init -g --codex`, `rtk init -g --agent cursor`, `rtk init -g --opencode`. rtk adds PreToolUse-style command rewriting and keeps other hooks; OpenMem's reinstall keeps rtk's entries. `rtk init` is idempotent. `--no-rtk` skips it.
26
+
27
+ **Context proxy.** `--context-proxy` creates `~/.config/personal-memory/context-proxy.env` (mode 600) with `LITELLM_MASTER_KEY` once and reuses it. Claude Code gets `env.ANTHROPIC_BASE_URL = http://127.0.0.1:<port>` and `env.ANTHROPIC_CUSTOM_HEADERS = x-litellm-api-key: Bearer <key>` in `~/.claude/settings.json`; OpenCode gets `provider.anthropic.options.baseURL = http://127.0.0.1:<port>/v1` and the same header ([OpenCode providers](https://opencode.ai/docs/providers/#base-url)). Claude Code then fails until the proxy runs. The proxy trims long sessions and compresses tool results; typing `/smart-compact` makes the prompt hook flag the next request for a trim. See [Context proxy](context-proxy.md). `--no-context-proxy` removes those keys when they still point at the loopback proxy; the key file stays. Codex stays hook-only. OpenCode 2 is never pointed at the proxy, even with `--context-proxy`; see below.
28
+
29
+ **OpenCode 1 or 2.** The installer runs `opencode --version`; output naming major 2 or later (OpenCode 2.2.1 prints `opencode v2.2.1`) selects OpenCode 2, and any failure or other output selects OpenCode 1. OpenCode 1 gets the file entry `cli/coding-agent/opencode-plugin.mjs` under `"plugin"`. OpenCode 2 gets the runtime directory `cli/coding-agent/opencode2-plugin` under `"plugins"` ([OpenCode 2 plugin config](https://opencode.ai/v2/docs/plugins/); configured local paths must be directories). Each install removes OpenMem's entry of the other shape, so only one OpenMem plugin loads. On OpenCode 2 the plugin trims context itself in its `context` hook, with the proxy's rules, thresholds (`CONTEXT_TRIM_AT_TOKENS`, `CONTEXT_COMPRESS_OVER_TOKENS`, `CONTEXT_COMPACTION_MAX_CHARS`, read from the environment OpenCode runs in), standing system-prompt instruction (`CONTEXT_STANDING_INSTRUCTION`) and [context store](context-store.md) files: every message goes into the session database `sessions/<sid>.db` through `node:sqlite` (Node ≥ 24). The installer therefore removes any earlier `provider.anthropic` proxy settings and says so in its summary. When OpenCode 2 compacts a session itself, the plugin's `compaction` hook marks every stored message out of context and supplies the `smart_compact_search` pointer plus bounded recall as the summary, so no model call is made. `[compaction] native = false` returns compaction to OpenCode's own summary.
30
+
31
+ This stages a persistent runtime under `~/.local/share/personal-memory/agent-runtime`, registers the unified MCP globally in Claude Code, Codex, Cursor Agent, and OpenCode, and installs their session hooks. It also copies the [openmem skill](../.agents/skills/openmem/SKILL.md) into each agent's global skill directory. The runtime path stays valid after an `npx` cache cleanup. Re-run the command after upgrading this package to refresh the runtime and skill. See [hook parity](coding-agent-hooks.md).
12
32
 
13
- For Codex, installation enables `features.hooks` when unset and preserves an explicit `false`. For OpenCode, the plugin and MCP entry go into the existing `opencode.jsonc` or `opencode.json`; unrelated settings and JSONC comments outside the updated fields are retained.
33
+ For Codex, installation sets `sandbox_mode = "workspace-write"` and enables `features.hooks` and `sandbox_workspace_write.network_access` when unset, and preserves explicit values. Codex otherwise defaults to a read-only sandbox without network, which breaks `curl` and package installs. For OpenCode, the plugin and MCP entry go into the existing `opencode.jsonc` or `opencode.json`; unrelated settings and JSONC comments outside the updated fields are retained.
14
34
 
15
35
  Codex requires review of new or changed non-managed hooks in `/hooks` before they run. [Codex hook documentation](https://developers.openai.com/codex/hooks) describes this trust step.
16
36
 
17
- The command keeps existing agent settings and other MCP servers. It does not remove the optional Hindsight plugin; after checking the new MCP and hooks, remove the old plugin through its own uninstaller if you no longer want both integrations running.
37
+ The command keeps existing agent settings and other MCP servers. With `integration = "openmem"` it removes what the upstream Hindsight plugin installed: its hook entries, its `hindsight` MCP server (`~/.claude.json`, `~/.codex/config.toml`, `~/.cursor/mcp.json`) and its `hindsight-coding-agent` skill (`~/.claude/skills`, `~/.agents/skills`, `~/.cursor/skills`). An MCP entry goes only when it runs the upstream `hindsight-coding-agents/dist/mcp-server.js`; a skill only when its `SKILL.md` declares `name: hindsight-coding-agent`. A `hindsight` MCP entry of your own that points elsewhere stays. `--dry-run` lists the removals; `provider` mode removes none of these.
18
38
 
19
- The wizard coding-agent step follows the same idea for Hindsight-first flows.
39
+ Wizard step 8 (Coding agents) offers the same `install-coding-agent --yes` for detected CLIs.
20
40
 
21
41
  Use a **real** `~/.hindsight/coding-agent.json` on disk (seed from `config/coding-agent.json`). Do not symlink into the repo — machine fields such as `optInPaths` and `notebooklmAccess` must not live in the checkout. The indexer reads this file through the `$HOME` mount. If it is missing, the indexer logs a warning and still indexes everything under `$HOME`.
22
42
 
@@ -26,7 +46,7 @@ To register **only** Cursor's memory-related MCP entries (NotebookLM, mem0, Cogn
26
46
 
27
47
  ```sh
28
48
  npx @bcmyguest/openmem install-mcp --yes
29
- # checkout: npx . install-mcp --yes
49
+ # checkout: node cli/openmem.mjs install-mcp --yes
30
50
 
31
51
  # mem0 self-hosted REST → @yeyuan98/mem0-mcp via cli/mem0-mcp.mjs
32
52
  MEMORY_PROVIDER=mem0 npx @bcmyguest/openmem install-mcp --yes --provider-only
@@ -45,7 +65,7 @@ To change `MEMORY_PROVIDER` (updates `~/.config/hindsight/wizard.conf`, optional
45
65
 
46
66
  ```sh
47
67
  npx @bcmyguest/openmem switch mem0 --yes
48
- # checkout: npx . switch cognee --yes
68
+ # checkout: node cli/openmem.mjs switch cognee --yes
49
69
  ```
50
70
 
51
71
  Flags: `--dry-run`, `--no-stack` (skip compose down/up), `--no-mcp` (skip `install-mcp --provider-only`). Switching does **not** migrate memories between backends. After a switch: restart Cursor Agent, then `npx @bcmyguest/openmem doctor`.