sessionmem 1.4.0 → 1.5.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/CHANGELOG.md +44 -0
- package/README.md +186 -41
- package/dist/adapters/diagnostics.js +109 -0
- package/dist/adapters/generic.js +6 -2
- package/dist/adapters/global/antigravity.js +4 -1
- package/dist/adapters/global/claudeCode.js +19 -4
- package/dist/adapters/global/codex.js +7 -6
- package/dist/adapters/global/qcoder.js +4 -1
- package/dist/adapters/ide/cline.js +3 -0
- package/dist/adapters/ide/cursor.js +3 -0
- package/dist/adapters/ide/installer.js +17 -5
- package/dist/adapters/ide/windsurf.js +3 -0
- package/dist/adapters/installedHosts.js +51 -0
- package/dist/cli/commands/doctor.js +146 -0
- package/dist/cli/commands/entities.js +27 -0
- package/dist/cli/commands/history.js +23 -0
- package/dist/cli/commands/ingestHook.js +4 -78
- package/dist/cli/commands/install.js +34 -56
- package/dist/cli/commands/scan.js +16 -0
- package/dist/cli/commands/search.js +1 -0
- package/dist/cli/commands/team.js +7 -3
- package/dist/cli/commands/ui.js +67 -0
- package/dist/cli/commands/watch.js +48 -0
- package/dist/cli/index.js +42 -1
- package/dist/cli/projectId.js +1 -2
- package/dist/cli/transcriptSweep.js +182 -0
- package/dist/core/api/contracts.js +1 -0
- package/dist/core/api/memoryCoreService.js +8 -0
- package/dist/core/api/sessionLifecycleService.js +13 -1
- package/dist/core/capture/toolEvent.js +82 -0
- package/dist/core/extract/factClaims.js +97 -0
- package/dist/core/extract/knowledge.js +127 -0
- package/dist/core/retrieve/retrieveMemories.js +20 -4
- package/dist/core/schema/migrations/012_transcript_checkpoints.sql +4 -0
- package/dist/core/schema/migrations/013_fts_porter.sql +32 -0
- package/dist/core/schema/migrations/014_memory_entities.sql +19 -0
- package/dist/core/schema/migrations/015_memory_supersessions.sql +41 -0
- package/dist/core/schema/runMigrations.js +46 -0
- package/dist/core/storage/knowledgeRepo.js +67 -0
- package/dist/core/storage/memoryRepo.js +45 -12
- package/dist/core/storage/memorySearchRepo.js +30 -20
- package/dist/core/storage/supersessionRepo.js +101 -0
- package/dist/core/summarize/summaryShape.js +1 -1
- package/dist/ui/assets.js +181 -0
- package/dist/ui/server.js +147 -0
- package/docs/REGISTRY_LISTING.md +88 -0
- package/docs/architecture.md +27 -3
- package/docs/benchmark.md +105 -33
- package/docs/migration.md +2 -0
- package/docs/troubleshooting.md +52 -4
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,40 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [1.5.0] - 2026-10-18
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- `sessionmem scan` to import existing Claude Code transcripts into memory, and
|
|
13
|
+
`sessionmem watch` to capture new and updated sessions continuously. Both are
|
|
14
|
+
idempotent across reruns and restarts and work fully offline. ([#45])
|
|
15
|
+
- Automatic host detection during `sessionmem install`, and a `sessionmem doctor`
|
|
16
|
+
command that checks the store, hooks, adapter wiring, and recall with fix
|
|
17
|
+
hints for each failure. ([#46])
|
|
18
|
+
- Deterministic entity and relation extraction during capture and scan, a
|
|
19
|
+
`sessionmem entities` command, and entity overlap as a recall ranking
|
|
20
|
+
signal. ([#48])
|
|
21
|
+
- Deterministic supersession handling: memories that replace an earlier fact are
|
|
22
|
+
linked with history, and recall prefers current facts while replaced ones stay
|
|
23
|
+
retrievable explicitly. ([#49])
|
|
24
|
+
- `sessionmem ui`, a local read-only web viewer for memories, entities,
|
|
25
|
+
relations, and supersession history, served on localhost with no external
|
|
26
|
+
services. ([#50])
|
|
27
|
+
- COMPARISON.md, a cited comparison with claude-mem, Mem0, and Zep, plus a draft
|
|
28
|
+
MCP registry listing. ([#51])
|
|
29
|
+
- A reproducible offline LoCoMo evaluation: committed corpus with provenance and
|
|
30
|
+
checksums, and a runner reporting hit rate, precision, recall, and latency
|
|
31
|
+
with all network access blocked. ([#44])
|
|
32
|
+
|
|
33
|
+
### Changed
|
|
34
|
+
|
|
35
|
+
- Retrieval now ranks with SQLite FTS5 BM25 and Porter stemming over memory
|
|
36
|
+
content and summaries. Measured on the committed LoCoMo evaluation, top-5 hit
|
|
37
|
+
rate improved from 1.44% to 61.95%, and to 63.92% with entity overlap. ([#47], [#48])
|
|
38
|
+
- The release workflow skips the npm publish when the version already exists on
|
|
39
|
+
the registry and creates the GitHub release with notes from this
|
|
40
|
+
changelog. ([#43])
|
|
41
|
+
|
|
8
42
|
## [1.4.0] - 2026-10-16
|
|
9
43
|
|
|
10
44
|
### Added
|
|
@@ -68,6 +102,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
68
102
|
errors and findings fail the job; added scanner dependency updates and
|
|
69
103
|
Dependabot cooldowns. ([#32])
|
|
70
104
|
|
|
105
|
+
[1.5.0]: https://github.com/catfish-1234/sessionmem/compare/679b8954f0b49f197985c40da47f181c00060cd9...v1.5.0
|
|
106
|
+
[#43]: https://github.com/catfish-1234/sessionmem/pull/43
|
|
107
|
+
[#44]: https://github.com/catfish-1234/sessionmem/pull/44
|
|
108
|
+
[#45]: https://github.com/catfish-1234/sessionmem/pull/45
|
|
109
|
+
[#46]: https://github.com/catfish-1234/sessionmem/pull/46
|
|
110
|
+
[#47]: https://github.com/catfish-1234/sessionmem/pull/47
|
|
111
|
+
[#48]: https://github.com/catfish-1234/sessionmem/pull/48
|
|
112
|
+
[#49]: https://github.com/catfish-1234/sessionmem/pull/49
|
|
113
|
+
[#50]: https://github.com/catfish-1234/sessionmem/pull/50
|
|
114
|
+
[#51]: https://github.com/catfish-1234/sessionmem/pull/51
|
|
71
115
|
[1.4.0]: https://github.com/catfish-1234/sessionmem/compare/a73f503123b1d33c35407ce66403f281ba3451ac...679b8954f0b49f197985c40da47f181c00060cd9
|
|
72
116
|
[#22]: https://github.com/catfish-1234/sessionmem/pull/22
|
|
73
117
|
[#23]: https://github.com/catfish-1234/sessionmem/pull/23
|
package/README.md
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
<a href="https://github.com/catfish-1234/sessionmem/blob/main/LICENSE"><img src="https://img.shields.io/badge/License-MIT-yellow.svg" alt="License: MIT"></a>
|
|
10
10
|
</p>
|
|
11
11
|
|
|
12
|
-
**Project memory between sessions, stored on your machine
|
|
12
|
+
**Project memory between sessions, stored on your machine.**
|
|
13
13
|
|
|
14
14
|
```
|
|
15
15
|
New session. Claude starts fresh.
|
|
@@ -49,16 +49,25 @@ Run this in your project folder after configuring your AI tool (Claude Code, Cur
|
|
|
49
49
|
|
|
50
50
|
```bash
|
|
51
51
|
sessionmem install
|
|
52
|
-
#
|
|
52
|
+
# Optional: select one host explicitly, including in automation:
|
|
53
53
|
sessionmem install --adapter claude-code
|
|
54
54
|
# Other names: cursor, cline, codex, windsurf, antigravity, qcoder, generic
|
|
55
55
|
```
|
|
56
56
|
|
|
57
|
-
|
|
57
|
+
`install` detects installed hosts from local executables, including from a plain
|
|
58
|
+
terminal, and configures every host found. Automatic discovery currently supports
|
|
59
|
+
Claude Code on `PATH` or in its standard native install locations. Use
|
|
60
|
+
`--adapter <name>` for the other adapters or to override discovery. The existing
|
|
61
|
+
`SESSIONMEM_ADAPTER` environment override also works; the flag takes precedence.
|
|
62
|
+
If no supported host is found, the command says so and exits nonzero before
|
|
63
|
+
creating files. Discovery and configuration work fully offline.
|
|
64
|
+
|
|
65
|
+
This initializes the database and, for each selected adapter:
|
|
58
66
|
|
|
59
67
|
- Registers `sessionmem` with your AI tool's MCP host so the host can start it automatically.
|
|
60
68
|
- Creates `~/.sessionmem/config.json` with secret redaction enabled and a 90-day retention window, if the file doesn't already exist.
|
|
61
69
|
- Adds instructions to the selected host's guidance file, such as `~/.claude/CLAUDE.md` for Claude Code or `~/.codex/AGENTS.md` for Codex. You can re-run the command without duplicating those instructions.
|
|
70
|
+
- Installs Claude Code's SessionStart, SessionEnd, and PostToolUse hooks and prints what was configured per host. If a host fails, the remaining hosts are still attempted and the command exits nonzero.
|
|
62
71
|
|
|
63
72
|
### 3. Restart your AI tool
|
|
64
73
|
|
|
@@ -73,10 +82,12 @@ After restarting:
|
|
|
73
82
|
- In Claude Code, the installed PostToolUse, SessionEnd, and SessionStart hooks capture supported tool events, summarize them, and load prior memories automatically.
|
|
74
83
|
- Other MCP hosts use sessionmem's tools and installed instructions. Automatic capture and session lifecycle calls depend on the host or agent. MCP registration alone does not install Claude Code hooks there.
|
|
75
84
|
|
|
76
|
-
Check
|
|
85
|
+
Check the installation offline:
|
|
77
86
|
|
|
78
87
|
```bash
|
|
79
|
-
sessionmem
|
|
88
|
+
sessionmem doctor
|
|
89
|
+
# For an explicitly selected host:
|
|
90
|
+
sessionmem doctor --adapter claude-code
|
|
80
91
|
```
|
|
81
92
|
|
|
82
93
|
---
|
|
@@ -139,36 +150,21 @@ The SQLite file is easy to inspect, back up, or delete, and you can use it acros
|
|
|
139
150
|
|
|
140
151
|
## Benchmark results
|
|
141
152
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
> **~85.6% reduction** in tokens compared to carrying full session history.
|
|
147
|
-
|
|
148
|
-
| | Tokens |
|
|
149
|
-
|---|---|
|
|
150
|
-
| Full session history (baseline) | 1,587 |
|
|
151
|
-
| What sessionmem injects at the start of your next session | 228 |
|
|
152
|
-
|
|
153
|
-
In this benchmark, about 1,600 tokens of session history become roughly 230 tokens of decisions, warnings, and facts for the next session.
|
|
154
|
-
|
|
155
|
-
### Retrieval accuracy
|
|
153
|
+
`npm run benchmark` evaluates conversational retrieval on the committed LoCoMo
|
|
154
|
+
corpus: 10 conversations, 272 sessions, 5,882 turns, and 1,527 scored questions.
|
|
155
|
+
It captures dialogue through the public `storeMemory` API and asks the original
|
|
156
|
+
questions through `retrieveMemories`, fully offline with no API keys.
|
|
156
157
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
| Recall | 100.0% |
|
|
163
|
-
| Precision | 33.3% |
|
|
164
|
-
|
|
165
|
-
Each query returns 3 candidate memories, with one scored as the expected match. That gives 33.3% precision even when the expected memory is found every time. The other two candidates may provide context, but are not scored as matches. These results apply to the 10 curated queries; see the report for the embedding model's limits.
|
|
166
|
-
|
|
167
|
-
The benchmark is deterministic and reproducible:
|
|
158
|
+
The evaluation reports hit rate, precision, recall, complete-evidence retrieval,
|
|
159
|
+
and latency. Production retrieval uses offline BM25 ranking with English stemming;
|
|
160
|
+
conversational questions test recall beyond exact copies of memory text.
|
|
161
|
+
See [benchmark results](docs/benchmark.md) and the
|
|
162
|
+
[methodology, dataset license, and metric definitions](benchmarks/README.md).
|
|
168
163
|
|
|
169
164
|
```bash
|
|
170
|
-
npm run build
|
|
171
|
-
npm run benchmark
|
|
165
|
+
npm run build
|
|
166
|
+
npm run benchmark
|
|
167
|
+
npm run benchmark -- --output benchmarks/results/locomo.json
|
|
172
168
|
```
|
|
173
169
|
|
|
174
170
|
---
|
|
@@ -213,16 +209,22 @@ See [`docs/architecture.md`](docs/architecture.md) for the technical overview.
|
|
|
213
209
|
|
|
214
210
|
| Command | What it does |
|
|
215
211
|
|---------|--------------|
|
|
216
|
-
| `sessionmem install [--adapter <name>]` |
|
|
212
|
+
| `sessionmem install [--adapter <name>]` | Detect installed hosts, register sessionmem with each, and write default config; the flag selects one host. |
|
|
213
|
+
| `sessionmem doctor [--adapter <name>]` | Check the store, schema, hooks, MCP wiring, and canary recall offline; print PASS/FAIL and repair hints. |
|
|
217
214
|
| `sessionmem uninstall [--adapter <name>] [--purge]` | Remove sessionmem from the host. `--purge` also deletes the local database. |
|
|
218
215
|
| `sessionmem run` | Start the MCP server. |
|
|
216
|
+
| `sessionmem ui [--port N]` | Open the local read-only web viewer (default port 3777). |
|
|
219
217
|
| `sessionmem ping` | Check the local version and database reachability. |
|
|
220
|
-
| `sessionmem search <query> [--limit <n>]` | Search memories by
|
|
218
|
+
| `sessionmem search <query> [--limit <n>] [--include-history]` | Search current memories by text query (limit 1–100, default 10). Include superseded facts with `--include-history`. |
|
|
221
219
|
| `sessionmem list` | List up to 200 recent memories for the current project; warns when truncated. |
|
|
220
|
+
| `sessionmem entities [--project <id>] [--json]` | List extracted entities, typed relations, and their source memory IDs. Defaults to the current project. |
|
|
221
|
+
| `sessionmem history [--project <id>] [--json]` | List supersession chains with old/new values, source memory IDs, reasons, and timestamps. |
|
|
222
222
|
| `sessionmem show <id>` | Show memory details (content capped at 2000 characters; use export for full content). |
|
|
223
223
|
| `sessionmem forget <id> [--force]` | Delete a memory by ID. |
|
|
224
224
|
| `sessionmem export [path]` | Export all current-project memories to JSON (default `~/.sessionmem/export-YYYY-MM-DD.json`). |
|
|
225
225
|
| `sessionmem import <path> [--merge]` | Import up to 1000 valid records from UTF-8 JSON into the current project. Existing IDs are preserved, including with `--merge`; invalid records are skipped with warnings. |
|
|
226
|
+
| `sessionmem scan [--transcripts-dir <path>]` | Import existing Claude Code JSONL transcripts into local session memories. Reports files found, sessions imported, memories created, skipped files, and errors. |
|
|
227
|
+
| `sessionmem watch [--once] [--interval <seconds>] [--transcripts-dir <path>]` | Capture new or updated Claude Code transcripts. Polls every 5 seconds by default; `--once` runs a single sweep. |
|
|
226
228
|
| `sessionmem stats` | Show memory statistics for the current project. |
|
|
227
229
|
| `sessionmem savings [--json]` | Show token savings from compression and injection, with percentage. |
|
|
228
230
|
| `sessionmem redact-scan [--apply]` | Scan stored memories for secrets; `--apply` redacts in place. |
|
|
@@ -253,9 +255,139 @@ for Windows paths. Imports accept CRLF and a UTF-8 BOM; convert other encodings
|
|
|
253
255
|
|
|
254
256
|
`SESSIONMEM_PROJECT_ID` pins the project bucket (useful when moving a repository);
|
|
255
257
|
otherwise it is derived from the repository root or working directory.
|
|
256
|
-
`SESSIONMEM_ADAPTER` selects the install/uninstall host unless `--adapter` is set.
|
|
258
|
+
`SESSIONMEM_ADAPTER` selects the install/doctor/uninstall host unless `--adapter` is set.
|
|
257
259
|
`SESSIONMEM_DEBUG=1` enables ingest-hook diagnostics on stderr.
|
|
258
260
|
|
|
261
|
+
### Inspect memories in your browser
|
|
262
|
+
|
|
263
|
+
```bash
|
|
264
|
+
sessionmem ui
|
|
265
|
+
sessionmem ui --port 8080
|
|
266
|
+
sessionmem ui --port 0 # Pick an available port
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
The command prints its URL and opens your default browser. It binds only to
|
|
270
|
+
`127.0.0.1`, needs no authentication, and serves all assets locally using Node's
|
|
271
|
+
built-in HTTP server. No external services, CDNs, or additional runtime dependencies
|
|
272
|
+
are used. If browser launch is unavailable, open the printed URL manually. Press
|
|
273
|
+
Ctrl-C to stop the server and close its database connection.
|
|
274
|
+
|
|
275
|
+
The viewer starts on the current project, with a selector for other projects in
|
|
276
|
+
the same store. Browse saved memories, search through the existing recall path,
|
|
277
|
+
inspect extracted entities and relations with their source memories, and follow
|
|
278
|
+
supersession chains with old/new values, source IDs, assertion times, detection
|
|
279
|
+
times, and partial/full status. Search returns up to 50 results and defaults to
|
|
280
|
+
current facts; select **Include superseded facts in search** to recall history.
|
|
281
|
+
Browsing lists all saved records, including superseded and expired ones, in pages
|
|
282
|
+
of 50. Source IDs open full memory details. Entity visibility and history links
|
|
283
|
+
use the same rules as `sessionmem entities` and `sessionmem history`.
|
|
284
|
+
|
|
285
|
+
The database is opened read-only. Browsing and on-demand search do not increment
|
|
286
|
+
access counts, migrate the schema, or change memories. Only HTTP GET and HEAD are
|
|
287
|
+
accepted; no write, delete, or edit routes are exposed. Responses are not cached,
|
|
288
|
+
and cross-origin browser requests and unexpected Host headers are rejected. Run
|
|
289
|
+
`sessionmem ping` first to initialize a missing store or upgrade an older schema.
|
|
290
|
+
The usual `SESSIONMEM_DB_PATH` and `SESSIONMEM_PROJECT_ID` overrides
|
|
291
|
+
apply. Anyone with access to this computer can read the local viewer while it is
|
|
292
|
+
running; stop it when finished inspecting private memories.
|
|
293
|
+
|
|
294
|
+
### Import existing sessions and watch for new ones
|
|
295
|
+
|
|
296
|
+
When a fact changes, sessionmem can link its replacement locally. For example, “I use VS Code” followed by “I switched to Neovim” supersedes the earlier editor choice. Recall excludes fully superseded memories by default. `retrieveMemories` accepts the optional `includeHistory: true`; `sessionmem search --include-history` searches earlier facts too. `sessionmem history` shows what replaced what. The old memory stays available through show, list, and export.
|
|
297
|
+
|
|
298
|
+
Detection uses deterministic entity/relation rules, shared purpose words, a small tool-category vocabulary, and explicit negation or replacement phrases. It needs no API or model. Unfamiliar wording can be missed, and ambiguous changes can be misclassified. A memory with independent claims remains searchable while any claim is current; history labels these partial supersessions. See [supersession details](docs/architecture.md#contradiction-and-supersession).
|
|
299
|
+
|
|
300
|
+
Run a scan to capture Claude Code sessions from before sessionmem was installed:
|
|
301
|
+
|
|
302
|
+
```bash
|
|
303
|
+
sessionmem scan
|
|
304
|
+
sessionmem watch --once
|
|
305
|
+
sessionmem watch --interval 10
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
Both commands recursively read `~/.claude/projects/**/*.jsonl`. Use
|
|
309
|
+
`--transcripts-dir "/path/to/projects"` to select another transcript tree. They
|
|
310
|
+
work fully offline without API keys and always use the local summarizer, even
|
|
311
|
+
if cloud summarization is enabled elsewhere.
|
|
312
|
+
|
|
313
|
+
Transcript user/assistant text and supported tool uses (Bash, Edit, Write,
|
|
314
|
+
MultiEdit) pass through the existing event-ingestion, redaction, and session-summary
|
|
315
|
+
pipeline. Each session gets one summary memory, including short sessions with
|
|
316
|
+
only one event. Updated sessions refresh that summary. Project buckets come from
|
|
317
|
+
each transcript's `cwd`, using its repository root when available; the directory
|
|
318
|
+
where you run scan/watch does not select the project. `SESSIONMEM_PROJECT_ID`
|
|
319
|
+
can explicitly pin all imports to one bucket. Transcripts without a working
|
|
320
|
+
directory or capturable events are skipped; nested subagent files get separate
|
|
321
|
+
session summaries.
|
|
322
|
+
|
|
323
|
+
Re-running either command does not duplicate memories or transcript events.
|
|
324
|
+
File checkpoints live in the selected memory database, and unchanged files are
|
|
325
|
+
skipped across restarts. Stable event IDs also make interrupted imports safe to
|
|
326
|
+
retry. Partial or malformed JSONL lines are reported and retried on later sweeps;
|
|
327
|
+
valid events in the same file can still be captured. A single sweep exits with
|
|
328
|
+
status 1 if it encounters errors. The daemon keeps running and retries errors.
|
|
329
|
+
|
|
330
|
+
`watch` prints its first sweep and subsequent imports or errors. It runs in the
|
|
331
|
+
foreground until SIGINT or SIGTERM. For a background process:
|
|
332
|
+
|
|
333
|
+
```bash
|
|
334
|
+
nohup sessionmem watch > sessionmem-watch.log 2>&1 &
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
For macOS launchd, save this as
|
|
338
|
+
`~/Library/LaunchAgents/io.sessionmem.watch.plist`. Replace the executable path
|
|
339
|
+
with the output of `command -v sessionmem`, and adjust PATH for your Node install:
|
|
340
|
+
|
|
341
|
+
```xml
|
|
342
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
343
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
344
|
+
<plist version="1.0">
|
|
345
|
+
<dict>
|
|
346
|
+
<key>Label</key><string>io.sessionmem.watch</string>
|
|
347
|
+
<key>ProgramArguments</key>
|
|
348
|
+
<array><string>/opt/homebrew/bin/sessionmem</string><string>watch</string></array>
|
|
349
|
+
<key>EnvironmentVariables</key>
|
|
350
|
+
<dict><key>PATH</key><string>/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin</string></dict>
|
|
351
|
+
<key>RunAtLoad</key><true/>
|
|
352
|
+
<key>KeepAlive</key><true/>
|
|
353
|
+
</dict>
|
|
354
|
+
</plist>
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
```bash
|
|
358
|
+
launchctl bootstrap "gui/$(id -u)" ~/Library/LaunchAgents/io.sessionmem.watch.plist
|
|
359
|
+
# Stop and unload:
|
|
360
|
+
launchctl bootout "gui/$(id -u)/io.sessionmem.watch"
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
For a Linux systemd user service, save this as
|
|
364
|
+
`~/.config/systemd/user/sessionmem-watch.service`. Replace the executable path
|
|
365
|
+
and adjust PATH for your Node install:
|
|
366
|
+
|
|
367
|
+
```ini
|
|
368
|
+
[Unit]
|
|
369
|
+
Description=Capture Claude Code transcripts with sessionmem
|
|
370
|
+
|
|
371
|
+
[Service]
|
|
372
|
+
ExecStart=/usr/local/bin/sessionmem watch
|
|
373
|
+
Environment=PATH=/usr/local/bin:/usr/bin:/bin
|
|
374
|
+
Restart=on-failure
|
|
375
|
+
RestartSec=5
|
|
376
|
+
|
|
377
|
+
[Install]
|
|
378
|
+
WantedBy=default.target
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
```bash
|
|
382
|
+
systemctl --user daemon-reload
|
|
383
|
+
systemctl --user enable --now sessionmem-watch.service
|
|
384
|
+
journalctl --user -u sessionmem-watch.service -f
|
|
385
|
+
# Stop and disable:
|
|
386
|
+
systemctl --user disable --now sessionmem-watch.service
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
These are optional examples; `sessionmem install` does not register a daemon.
|
|
390
|
+
|
|
259
391
|
---
|
|
260
392
|
|
|
261
393
|
## Privacy, secrets, and your data
|
|
@@ -296,13 +428,13 @@ Old decisions and duplicate facts can accumulate in a memory store, causing the
|
|
|
296
428
|
sessionmem retention prune --force # actually deletes
|
|
297
429
|
```
|
|
298
430
|
|
|
299
|
-
2. **Importance-weighted ranking**: retrieval ranks memories by
|
|
431
|
+
2. **Importance-weighted ranking**: retrieval ranks memories by offline BM25 lexical relevance, entity overlap, recency, and importance. Older, less important notes rank lower even before they are pruned.
|
|
300
432
|
|
|
301
433
|
3. **Token-budgeted injection**: only the highest-ranked memories are loaded, trimmed to a small token budget (see [benchmarks](#benchmark-results)). The context stays within that budget as the store grows.
|
|
302
434
|
|
|
303
435
|
4. **Conflict resolution in team mode**: merging teammate memories uses last-write-wins by id to avoid stale duplicates and keeps the higher importance score so a warning isn't downgraded.
|
|
304
436
|
|
|
305
|
-
The benchmark
|
|
437
|
+
The [conversational benchmark](benchmarks/README.md) measures retrieval quality separately from retention and startup token budgets.
|
|
306
438
|
|
|
307
439
|
Export your memories before pruning if you want to keep a copy:
|
|
308
440
|
|
|
@@ -359,7 +491,7 @@ Other tools that implement the [Model Context Protocol](https://modelcontextprot
|
|
|
359
491
|
## Further documentation
|
|
360
492
|
|
|
361
493
|
- [Architecture](docs/architecture.md): how the core engine, adapters, CLI, and SQLite storage fit together.
|
|
362
|
-
- [Benchmark](docs/benchmark.md):
|
|
494
|
+
- [Benchmark](docs/benchmark.md): conversational retrieval results and reproduction steps.
|
|
363
495
|
- [Privacy and retention](docs/privacy-and-retention.md): secret redaction, retention pruning, and config.
|
|
364
496
|
- [Team mode](docs/team-mode.md): shared-path team memory.
|
|
365
497
|
- [Cloud summarization](docs/cloud-summarization.md): the opt-in cloud summarization path.
|
|
@@ -372,13 +504,26 @@ Other tools that implement the [Model Context Protocol](https://modelcontextprot
|
|
|
372
504
|
|
|
373
505
|
For install failures, adapter issues, missing session data, or `better-sqlite3` native-build problems, see [`docs/troubleshooting.md`](docs/troubleshooting.md).
|
|
374
506
|
|
|
375
|
-
|
|
507
|
+
Start with offline diagnostics:
|
|
376
508
|
|
|
377
509
|
```bash
|
|
378
|
-
sessionmem
|
|
379
|
-
sessionmem
|
|
510
|
+
sessionmem doctor
|
|
511
|
+
sessionmem doctor --adapter claude-code # explicit host override
|
|
380
512
|
```
|
|
381
513
|
|
|
514
|
+
`doctor` reports a separate PASS or FAIL for the memory store, schema, hook
|
|
515
|
+
definitions shipped with the current version, configured MCP command, and a
|
|
516
|
+
canary write/recall. It exits nonzero if any check fails and prints fix hints,
|
|
517
|
+
including exact commands for safe repairs. It does not create a missing store or
|
|
518
|
+
apply migrations. Its temporary canary is isolated from your projects and rolled
|
|
519
|
+
back even on failure. The MCP check starts the installed local command and verifies
|
|
520
|
+
its version, tools, and a stats response; it does not require a running agent.
|
|
521
|
+
|
|
522
|
+
Use `sessionmem ping` to initialize a missing store or apply pending schema
|
|
523
|
+
migrations, or `sessionmem install --adapter claude-code` to refresh Claude Code's
|
|
524
|
+
MCP registration and hooks. Run repairs with the same `SESSIONMEM_DB_PATH` as
|
|
525
|
+
doctor when using a custom store.
|
|
526
|
+
|
|
382
527
|
If `stats` shows `sessions: 0` after real work, see [“0 sessions” / no session data recorded](docs/troubleshooting.md#0-sessions--no-session-data-recorded). Memories are keyed to the **repository root**, so every directory inside one repo shares a bucket; outside a repo the working directory itself is the key.
|
|
383
528
|
|
|
384
529
|
To check database access and memory counts:
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
4
|
+
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
5
|
+
import { IDEInstaller, isManagedClaudeHook } from "./ide/installer.js";
|
|
6
|
+
import { resolveDbPath } from "../cli/context.js";
|
|
7
|
+
function object(value) {
|
|
8
|
+
return value !== null && typeof value === "object" && !Array.isArray(value)
|
|
9
|
+
? value
|
|
10
|
+
: {};
|
|
11
|
+
}
|
|
12
|
+
export function checkHooks(installation) {
|
|
13
|
+
const expected = installation.hooks;
|
|
14
|
+
if (!expected)
|
|
15
|
+
return "This host uses MCP tools; no lifecycle hooks are required";
|
|
16
|
+
const config = IDEInstaller.parseJsonc(readFileSync(expected.path, "utf8"));
|
|
17
|
+
if (config.disableAllHooks === true)
|
|
18
|
+
throw new Error("Claude Code has disableAllHooks enabled");
|
|
19
|
+
const hooks = object(config.hooks);
|
|
20
|
+
for (const spec of expected.entries) {
|
|
21
|
+
const entries = Array.isArray(hooks[spec.event]) ? hooks[spec.event] : [];
|
|
22
|
+
const managed = entries.flatMap((value) => {
|
|
23
|
+
const entry = object(value);
|
|
24
|
+
return Array.isArray(entry.hooks) ? entry.hooks.filter((value) => isManagedClaudeHook(object(value).command, spec.command)).map((hook) => ({ entry, hook: object(hook) })) : [];
|
|
25
|
+
});
|
|
26
|
+
const matches = managed.filter(({ entry, hook }) => {
|
|
27
|
+
if (entry.matcher !== spec.matcher || entry.async === true)
|
|
28
|
+
return false;
|
|
29
|
+
return hook.type === "command" && hook.command === spec.command && hook.async !== true;
|
|
30
|
+
});
|
|
31
|
+
if (managed.length !== 1 || matches.length !== 1)
|
|
32
|
+
throw new Error(`${spec.event} hook is missing, duplicated, or differs from the current definition`);
|
|
33
|
+
}
|
|
34
|
+
return `SessionStart, SessionEnd, PostToolUse match current hook definitions (${expected.path})`;
|
|
35
|
+
}
|
|
36
|
+
/** Check the installed launch entry before executing the known local command. */
|
|
37
|
+
export function readMcpLaunch(installation) {
|
|
38
|
+
const expected = installation.mcp;
|
|
39
|
+
const content = readFileSync(expected.path, "utf8");
|
|
40
|
+
let entry;
|
|
41
|
+
if (expected.format === "toml") {
|
|
42
|
+
// The Codex installer writes this small TOML section with JSON-compatible
|
|
43
|
+
// quoted values. Refuse unfamiliar syntax rather than claiming it works.
|
|
44
|
+
const section = content.match(/^\s*\[mcp_servers\.sessionmem\][^\S\r\n]*\r?\n([\s\S]*?)(?=^\s*\[|$(?![\s\S]))/m)?.[1];
|
|
45
|
+
if (!section)
|
|
46
|
+
throw new Error("sessionmem MCP section is missing");
|
|
47
|
+
if (/^\s*\[mcp_servers\.sessionmem\./m.test(content) || /^\s*(?:env|env_vars|cwd|url)\s*=/m.test(section)) {
|
|
48
|
+
throw new Error("Custom Codex launch settings require manual verification; doctor supports the installer-generated command and args");
|
|
49
|
+
}
|
|
50
|
+
if (/^\s*enabled\s*=\s*false\s*$/m.test(section))
|
|
51
|
+
throw new Error("sessionmem MCP server is disabled");
|
|
52
|
+
entry = {
|
|
53
|
+
command: JSON.parse(section.match(/^\s*command\s*=\s*(.+)$/m)?.[1] ?? "null"),
|
|
54
|
+
args: JSON.parse(section.match(/^\s*args\s*=\s*(.+)$/m)?.[1] ?? "null"),
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
const config = IDEInstaller.parseJsonc(content);
|
|
59
|
+
entry = object(object(config.mcpServers).sessionmem);
|
|
60
|
+
}
|
|
61
|
+
if (entry.disabled === true)
|
|
62
|
+
throw new Error("sessionmem MCP server is disabled");
|
|
63
|
+
if (entry.type !== undefined && entry.type !== "stdio")
|
|
64
|
+
throw new Error("sessionmem MCP server must use the local stdio transport");
|
|
65
|
+
if (entry.command !== expected.command || JSON.stringify(entry.args) !== JSON.stringify(expected.args)) {
|
|
66
|
+
throw new Error(`sessionmem MCP launch entry differs from the current definition (${expected.path})`);
|
|
67
|
+
}
|
|
68
|
+
if (entry.env !== undefined && (entry.env === null || typeof entry.env !== "object" || Array.isArray(entry.env))) {
|
|
69
|
+
throw new Error("sessionmem MCP environment must be an object");
|
|
70
|
+
}
|
|
71
|
+
const env = object(entry.env);
|
|
72
|
+
if (Object.values(env).some((value) => typeof value !== "string")) {
|
|
73
|
+
throw new Error("sessionmem MCP environment values must be strings");
|
|
74
|
+
}
|
|
75
|
+
return { command: expected.command, args: expected.args, env: env };
|
|
76
|
+
}
|
|
77
|
+
export async function probeMcpWiring(launch, dbPath, version) {
|
|
78
|
+
if (launch.env?.SESSIONMEM_DB_PATH && resolve(resolveDbPath(launch.env.SESSIONMEM_DB_PATH)) !== resolve(dbPath)) {
|
|
79
|
+
throw new Error("MCP configuration selects a different SESSIONMEM_DB_PATH; run doctor with that path");
|
|
80
|
+
}
|
|
81
|
+
const inherited = Object.fromEntries(Object.entries(process.env).filter((entry) => entry[1] !== undefined));
|
|
82
|
+
const client = new Client({ name: "sessionmem-doctor", version });
|
|
83
|
+
const transport = new StdioClientTransport({
|
|
84
|
+
command: launch.command,
|
|
85
|
+
args: launch.args,
|
|
86
|
+
env: { ...inherited, ...launch.env, SESSIONMEM_DB_PATH: dbPath },
|
|
87
|
+
stderr: "ignore",
|
|
88
|
+
});
|
|
89
|
+
try {
|
|
90
|
+
await client.connect(transport, { timeout: 5000 });
|
|
91
|
+
const server = client.getServerVersion();
|
|
92
|
+
if (server?.name !== "sessionmem" || server.version !== version) {
|
|
93
|
+
throw new Error(`MCP server version does not match sessionmem ${version}`);
|
|
94
|
+
}
|
|
95
|
+
const tools = await client.listTools({}, { timeout: 5000 });
|
|
96
|
+
for (const name of ["stats", "storeMemory", "retrieveMemories"]) {
|
|
97
|
+
if (!tools.tools.some((tool) => tool.name === name))
|
|
98
|
+
throw new Error(`MCP tool ${name} is missing`);
|
|
99
|
+
}
|
|
100
|
+
const result = await client.callTool({ name: "stats", arguments: {} }, undefined, { timeout: 5000 });
|
|
101
|
+
const payload = Array.isArray(result.content) ? object(result.content.find((part) => object(part).type === "text")) : {};
|
|
102
|
+
if (result.isError || typeof payload.text !== "string" || !JSON.parse(payload.text).ok) {
|
|
103
|
+
throw new Error("MCP stats tool failed");
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
finally {
|
|
107
|
+
await transport.close();
|
|
108
|
+
}
|
|
109
|
+
}
|
package/dist/adapters/generic.js
CHANGED
|
@@ -69,6 +69,7 @@ const TOOL_DEFINITIONS = [
|
|
|
69
69
|
mode: retrieveMemoriesRequestSchema.shape.mode.describe("'auto' for the standard startup context-load path. 'on-demand' for an explicit mid-session retrieval triggered by a specific task or question."),
|
|
70
70
|
depth: retrieveMemoriesRequestSchema.shape.depth.describe("'default' for standard semantic search. 'deep' for a broader sweep that surfaces less-similar memories — use when the topic is new or unfamiliar."),
|
|
71
71
|
tags: retrieveMemoriesRequestSchema.shape.tags.describe("Optional tag filter. When provided, only return memories that have ALL listed tags."),
|
|
72
|
+
includeHistory: retrieveMemoriesRequestSchema.shape.includeHistory.describe("Include superseded facts for historical questions. Omit for current facts only."),
|
|
72
73
|
},
|
|
73
74
|
},
|
|
74
75
|
{
|
|
@@ -209,18 +210,21 @@ export class GenericMCPAdapter {
|
|
|
209
210
|
guidanceTargets() {
|
|
210
211
|
return [join(process.cwd(), "AGENTS.md")];
|
|
211
212
|
}
|
|
213
|
+
installation() {
|
|
214
|
+
return { mcp: { path: join(process.cwd(), ".mcp.json"), command: "sessionmem", args: ["run"] } };
|
|
215
|
+
}
|
|
212
216
|
/**
|
|
213
217
|
* Fallback for hosts that aren't specifically detected: register sessionmem
|
|
214
218
|
* in a project-local `.mcp.json` (the de-facto generic MCP config format).
|
|
215
219
|
*/
|
|
216
220
|
async install() {
|
|
217
|
-
const configPath =
|
|
221
|
+
const configPath = this.installation().mcp.path;
|
|
218
222
|
return IDEInstaller.injectMcpConfig(configPath, "sessionmem", "sessionmem", [
|
|
219
223
|
"run",
|
|
220
224
|
]);
|
|
221
225
|
}
|
|
222
226
|
async uninstall() {
|
|
223
|
-
const configPath =
|
|
227
|
+
const configPath = this.installation().mcp.path;
|
|
224
228
|
return IDEInstaller.removeMcpConfig(configPath, "sessionmem");
|
|
225
229
|
}
|
|
226
230
|
async call(_method, _request) {
|
|
@@ -11,13 +11,16 @@ import { IDEInstaller } from "../ide/installer.js";
|
|
|
11
11
|
const ANTIGRAVITY_MCP_CONFIG = [".gemini", "config", "mcp_config.json"];
|
|
12
12
|
export class AntigravityAdapter extends GenericMCPAdapter {
|
|
13
13
|
name = "Antigravity";
|
|
14
|
+
installation() {
|
|
15
|
+
return { mcp: { path: join(homedir(), ...ANTIGRAVITY_MCP_CONFIG), command: "sessionmem", args: ["run"] } };
|
|
16
|
+
}
|
|
14
17
|
// Capabilities inherited from GenericMCPAdapter (tools only).
|
|
15
18
|
/** Antigravity reads AGENTS.md-style guidance from its global config dir. */
|
|
16
19
|
guidanceTargets() {
|
|
17
20
|
return [join(homedir(), ".antigravity", "AGENTS.md")];
|
|
18
21
|
}
|
|
19
22
|
async install() {
|
|
20
|
-
const configPath =
|
|
23
|
+
const configPath = this.installation().mcp.path;
|
|
21
24
|
return IDEInstaller.injectMcpConfig(configPath, "sessionmem", "sessionmem", [
|
|
22
25
|
"run",
|
|
23
26
|
]);
|
|
@@ -21,15 +21,30 @@ export class ClaudeCodeAdapter extends GenericMCPAdapter {
|
|
|
21
21
|
settingsPath() {
|
|
22
22
|
return join(homedir(), ".claude", "settings.json");
|
|
23
23
|
}
|
|
24
|
+
installation() {
|
|
25
|
+
const isWindows = process.platform === "win32";
|
|
26
|
+
return {
|
|
27
|
+
mcp: {
|
|
28
|
+
path: join(homedir(), ".claude.json"),
|
|
29
|
+
command: isWindows ? "cmd" : "sessionmem",
|
|
30
|
+
args: isWindows ? ["/c", "sessionmem", "run"] : ["run"],
|
|
31
|
+
},
|
|
32
|
+
hooks: {
|
|
33
|
+
path: this.settingsPath(),
|
|
34
|
+
entries: [
|
|
35
|
+
{ event: "SessionStart", command: SESSIONMEM_HOOK_COMMAND },
|
|
36
|
+
{ event: "SessionEnd", command: SESSIONMEM_SESSION_END_HOOK_COMMAND },
|
|
37
|
+
{ event: "PostToolUse", command: SESSIONMEM_POST_TOOL_HOOK_COMMAND, matcher: SESSIONMEM_POST_TOOL_MATCHER },
|
|
38
|
+
],
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
}
|
|
24
42
|
async install() {
|
|
25
|
-
const configPath = join(homedir(), ".claude.json");
|
|
26
43
|
// On Windows, the globally-installed `sessionmem` bin is a `.cmd` shim.
|
|
27
44
|
// Claude Code spawns MCP servers WITHOUT a shell, and `.cmd` shims require
|
|
28
45
|
// cmd.exe to execute, so a bare `command: "sessionmem"` fails to start.
|
|
29
46
|
// Route through `cmd /c sessionmem run` so the shim is resolved correctly.
|
|
30
|
-
const
|
|
31
|
-
const command = isWindows ? "cmd" : "sessionmem";
|
|
32
|
-
const args = isWindows ? ["/c", "sessionmem", "run"] : ["run"];
|
|
47
|
+
const { path: configPath, command, args } = this.installation().mcp;
|
|
33
48
|
const mcpOk = await IDEInstaller.injectMcpConfig(configPath, "sessionmem", command, args);
|
|
34
49
|
// Register a SessionStart hook so prior memories are injected automatically
|
|
35
50
|
// at the start of EVERY Claude Code session. This is the deterministic
|
|
@@ -12,6 +12,9 @@ args = ["run"]
|
|
|
12
12
|
`;
|
|
13
13
|
export class CodexAdapter extends GenericMCPAdapter {
|
|
14
14
|
name = "Codex";
|
|
15
|
+
installation() {
|
|
16
|
+
return { mcp: { path: join(homedir(), ".codex", "config.toml"), command: "sessionmem", args: ["run"], format: "toml" } };
|
|
17
|
+
}
|
|
15
18
|
// Capabilities inherited from GenericMCPAdapter (tools only).
|
|
16
19
|
/** Codex reads AGENTS.md; the global one lives in ~/.codex/AGENTS.md. */
|
|
17
20
|
guidanceTargets() {
|
|
@@ -20,21 +23,19 @@ export class CodexAdapter extends GenericMCPAdapter {
|
|
|
20
23
|
async install() {
|
|
21
24
|
try {
|
|
22
25
|
const { readFileSync, writeFileSync, existsSync, mkdirSync } = await import("fs");
|
|
23
|
-
const configPath =
|
|
26
|
+
const configPath = this.installation().mcp.path;
|
|
24
27
|
const existing = existsSync(configPath)
|
|
25
28
|
? readFileSync(configPath, "utf-8")
|
|
26
29
|
: "";
|
|
27
|
-
//
|
|
28
|
-
|
|
29
|
-
if (existing.split("\n").some((line) => line.trimStart().startsWith("[mcp_servers.sessionmem]")))
|
|
30
|
-
return true;
|
|
30
|
+
// Replace our section so re-install also repairs stale launch commands.
|
|
31
|
+
const withoutServer = existing.replace(/^\s*\[mcp_servers\.sessionmem\][^\S\r\n]*\r?\n[\s\S]*?(?=^\s*\[|$(?![\s\S]))/m, "");
|
|
31
32
|
const dir = dirname(configPath);
|
|
32
33
|
if (!existsSync(dir)) {
|
|
33
34
|
mkdirSync(dir, { recursive: true });
|
|
34
35
|
}
|
|
35
36
|
// Append the TOML block, trimming trailing whitespace to keep a single
|
|
36
37
|
// blank-line separator before our section.
|
|
37
|
-
const updated =
|
|
38
|
+
const updated = withoutServer.trimEnd() + "\n" + CODEX_MCP_BLOCK;
|
|
38
39
|
writeFileSync(configPath, updated, "utf-8");
|
|
39
40
|
return true;
|
|
40
41
|
}
|
|
@@ -17,13 +17,16 @@ import { IDEInstaller } from "../ide/installer.js";
|
|
|
17
17
|
// directory spelling, or wire it through the UI-managed store if no file exists.
|
|
18
18
|
export class QCoderAdapter extends GenericMCPAdapter {
|
|
19
19
|
name = "QCoder";
|
|
20
|
+
installation() {
|
|
21
|
+
return { mcp: { path: join(homedir(), ".qcoder", "config.json"), command: "sessionmem", args: ["run"] } };
|
|
22
|
+
}
|
|
20
23
|
// Capabilities inherited from GenericMCPAdapter (tools only).
|
|
21
24
|
/** QCoder reads AGENTS.md-style guidance from its global config dir. */
|
|
22
25
|
guidanceTargets() {
|
|
23
26
|
return [join(homedir(), ".qcoder", "AGENTS.md")];
|
|
24
27
|
}
|
|
25
28
|
async install() {
|
|
26
|
-
const configPath =
|
|
29
|
+
const configPath = this.installation().mcp.path;
|
|
27
30
|
return IDEInstaller.injectMcpConfig(configPath, "sessionmem", "sessionmem", [
|
|
28
31
|
"run",
|
|
29
32
|
]);
|
|
@@ -59,6 +59,9 @@ export class ClineAdapter extends GenericMCPAdapter {
|
|
|
59
59
|
// expects to read.
|
|
60
60
|
return IDEInstaller.injectMcpConfig(this.configPath, "sessionmem", "sessionmem", ["run"], { disabled: false, autoApprove: [] });
|
|
61
61
|
}
|
|
62
|
+
installation() {
|
|
63
|
+
return { mcp: { path: this.configPath, command: "sessionmem", args: ["run"] } };
|
|
64
|
+
}
|
|
62
65
|
async uninstall() {
|
|
63
66
|
return IDEInstaller.removeMcpConfig(this.configPath, "sessionmem");
|
|
64
67
|
}
|