@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.
- package/{skills → .agents/skills}/openmem/SKILL.md +5 -0
- package/README.md +80 -76
- package/cli/coding-agent/capabilities.mjs +14 -3
- package/cli/coding-agent/context-store.mjs +81 -0
- package/cli/coding-agent/context-trim.mjs +897 -0
- package/cli/coding-agent/file-io.mjs +72 -0
- package/cli/coding-agent/hook-harnesses.mjs +2 -1
- package/cli/coding-agent/hook-runner.mjs +28 -4
- package/cli/coding-agent/install-access.mjs +43 -17
- package/cli/coding-agent/install-context-proxy.mjs +333 -0
- package/cli/coding-agent/install-rtk.mjs +60 -0
- package/cli/coding-agent/install-shim.mjs +119 -15
- package/cli/coding-agent/opencode-config.mjs +21 -5
- package/cli/coding-agent/opencode-plugin.mjs +39 -3
- package/cli/coding-agent/opencode2-plugin/index.js +299 -0
- package/cli/coding-agent/recall-frame.mjs +29 -0
- package/cli/coding-agent/remove-upstream-access.mjs +122 -0
- package/cli/coding-agent/runtime.mjs +2 -1
- package/cli/coding-agent/session-db.mjs +626 -0
- package/cli/coding-agent/session-facade.mjs +30 -6
- package/cli/coding-agent/toml-edit.mjs +161 -0
- package/cli/context-management.mjs +254 -0
- package/cli/feed-sync.mjs +18 -2
- package/cli/install-coding-agent.mjs +173 -40
- package/cli/mcp/context-sql-runner.mjs +58 -0
- package/cli/mcp/context-store.mjs +670 -0
- package/cli/mcp/knowledge.mjs +333 -0
- package/cli/mcp/memory-facade.mjs +99 -33
- package/cli/openmem-mcp.mjs +3 -0
- package/cli/openmem.mjs +18 -1
- package/cli/schedule.mjs +176 -0
- package/cli/stack.mjs +2 -0
- package/cli/switch-memory-provider.mjs +1 -1
- package/compose.yaml +67 -4
- package/config/hindsight.env.example +2 -0
- package/config/openmem.toml.example +64 -2
- package/docs/architecture.md +35 -9
- package/docs/coding-agent-hooks.md +26 -2
- package/docs/coding-agents.md +27 -7
- package/docs/compatibility.md +80 -0
- package/docs/concepts.md +30 -7
- package/docs/context-management.md +255 -0
- package/docs/context-proxy.md +115 -0
- package/docs/context-research.md +215 -0
- package/docs/context-store.md +277 -0
- package/docs/hindsight.md +5 -2
- package/docs/mcp-setup.md +5 -2
- package/docs/mem0.md +39 -1
- package/docs/memory-providers.md +2 -2
- package/docs/meridian-litellm.md +198 -0
- package/docs/model-configuration.md +22 -0
- package/docs/npx-install.md +6 -4
- package/docs/openmem-config.md +2 -2
- package/docs/quickstart.md +12 -6
- package/docs/setup-wizard.md +5 -4
- package/docs/sources-feeds.md +9 -22
- package/package.json +5 -4
- package/providers/cognee/OPERATOR.md +2 -2
- package/providers/hindsight/lib/clack.mjs +122 -20
- package/providers/hindsight/lib/coding-agent.mjs +1 -1
- package/providers/hindsight/lib/config.mjs +6 -0
- package/providers/hindsight/lib/env-setup.mjs +2 -1
- package/providers/hindsight/lib/litellm-routes.mjs +21 -12
- package/providers/hindsight/lib/meridian.mjs +307 -0
- package/providers/hindsight/lib/model-backend.mjs +38 -9
- package/providers/hindsight/lib/probe.mjs +21 -0
- package/providers/hindsight/lib/stack-env.mjs +5 -12
- package/providers/hindsight/lib/state.mjs +6 -1
- package/providers/hindsight/lib/summary.mjs +16 -2
- package/providers/hindsight/lib/validate.mjs +6 -0
- package/providers/hindsight/wizard.mjs +3 -2
- package/providers/mem0/OPERATOR.md +21 -2
- package/providers/mem0/compose.yaml +24 -2
- package/providers/mem0/lib/coding-agent.mjs +1 -1
- package/providers/mem0/lib/configure.mjs +66 -0
- package/providers/mem0/lib/litellm.mjs +457 -0
- package/providers/mem0/lib/stack-env.mjs +40 -1
- package/providers/mem0/lib/summary.mjs +30 -2
- package/providers/mem0/manifest.json +1 -1
- package/providers/mem0/wizard.mjs +15 -2
- package/proxy/config.yaml +33 -0
- package/proxy/openmem_context/__init__.py +6 -0
- package/proxy/openmem_context/adapters.py +504 -0
- package/proxy/openmem_context/compress.py +53 -0
- package/proxy/openmem_context/core.py +606 -0
- package/proxy/openmem_context/embed.py +212 -0
- package/proxy/openmem_context/recall.py +49 -0
- package/proxy/openmem_context/session.py +70 -0
- package/proxy/openmem_context/sessiondb.py +276 -0
- package/proxy/openmem_context/settings.py +134 -0
- package/proxy/openmem_context/store.py +129 -0
- package/proxy/openmem_context/tokens.py +123 -0
- package/proxy/openmem_context/ttl.py +78 -0
- package/proxy/openmem_context_hook.py +34 -0
- package/scripts/check-pack.mjs +2 -1
- package/scripts/setup.mjs +14 -1
- package/server.json +2 -2
- package/wizard/lib/context-step.mjs +140 -0
- package/wizard/lib/litellm-env.mjs +55 -0
- package/wizard/lib/pm-config.mjs +212 -4
- package/wizard/lib/prompts.mjs +28 -0
- package/wizard/lib/run-wizard.mjs +26 -31
package/cli/schedule.mjs
ADDED
|
@@ -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("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """);
|
|
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
|
-
*
|
|
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.
|
|
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 = "~"
|
package/docs/architecture.md
CHANGED
|
@@ -1,6 +1,25 @@
|
|
|
1
1
|
# Architecture
|
|
2
2
|
|
|
3
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
|
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
|
|
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 --
|
|
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)
|
|
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 | `--
|
|
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](
|
|
28
|
-
- **
|
|
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).
|
package/docs/coding-agents.md
CHANGED
|
@@ -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
|
-
#
|
|
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
|
-
|
|
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
|
|
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.
|
|
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
|
-
|
|
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:
|
|
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:
|
|
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`.
|