sessionmem 1.2.0 → 1.4.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/.github/sessionmem-logo.svg +57 -0
- package/CHANGELOG.md +84 -0
- package/CONTRIBUTING.md +130 -0
- package/README.md +153 -82
- package/SECURITY.md +33 -0
- package/dist/cli/commands/dedupe.js +89 -22
- package/dist/cli/commands/import.js +15 -44
- package/dist/cli/commands/ingestHook.js +107 -18
- package/dist/cli/commands/install.js +3 -3
- package/dist/cli/commands/list.js +3 -0
- package/dist/cli/commands/run.js +10 -12
- package/dist/cli/commands/savings.js +3 -1
- package/dist/cli/commands/search.js +8 -4
- package/dist/cli/commands/stats.js +7 -0
- package/dist/cli/commands/uninstall.js +2 -1
- package/dist/cli/context.js +8 -6
- package/dist/cli/index.js +8 -17
- package/dist/cli/projectId.js +48 -2
- package/dist/core/api/contracts.js +6 -1
- package/dist/core/api/memoryCoreService.js +17 -3
- package/dist/core/config/policyConfig.js +5 -1
- package/dist/core/injection/tokenBudget.js +10 -2
- package/dist/core/retrieve/retrieveMemories.js +1 -1
- package/dist/core/schema/runMigrations.js +9 -5
- package/dist/core/storage/db.js +31 -9
- package/dist/core/storage/memoryRepo.js +1 -1
- package/dist/core/storage/memorySearchRepo.js +36 -14
- package/dist/core/storage/sessionEventsRepo.js +21 -0
- package/dist/core/summarize/cloudSummarizer.js +3 -8
- package/dist/core/summarize/redaction.js +70 -10
- package/dist/core/summarize/summaryShape.js +50 -20
- package/docs/architecture.md +53 -0
- package/docs/benchmark.md +50 -0
- package/docs/cloud-summarization.md +57 -0
- package/docs/migration.md +42 -0
- package/docs/privacy-and-retention.md +139 -0
- package/docs/team-mode.md +117 -0
- package/docs/troubleshooting.md +78 -0
- package/docs/typescript-upgrade.md +48 -0
- package/package.json +26 -13
- package/dist/core/session.js +0 -4
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
/** Max distinct lines rendered per section. */
|
|
2
|
+
const MAX_SECTION_LINES = 5;
|
|
3
|
+
/** Max `- [type] text` fact lines in the facts section. */
|
|
4
|
+
const MAX_FACT_LINES = 8;
|
|
5
|
+
/**
|
|
6
|
+
* Human-readable text for an event. The auto-ingest hook writes a `text` field
|
|
7
|
+
* carrying a one-line description ("Ran: npm test", "Edited src/foo.ts"); fall
|
|
8
|
+
* back to the raw payload for events written by other producers.
|
|
9
|
+
*/
|
|
1
10
|
function extractEventText(event) {
|
|
2
11
|
try {
|
|
3
12
|
const parsed = JSON.parse(event.payload_json);
|
|
@@ -10,34 +19,55 @@ function extractEventText(event) {
|
|
|
10
19
|
}
|
|
11
20
|
return event.payload_json.trim();
|
|
12
21
|
}
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
22
|
+
/** Distinct, non-empty lines, preserving first-seen order. */
|
|
23
|
+
function distinctLines(events) {
|
|
24
|
+
const seen = new Set();
|
|
25
|
+
const lines = [];
|
|
26
|
+
for (const event of events) {
|
|
27
|
+
const line = extractEventText(event);
|
|
28
|
+
if (line.length === 0 || seen.has(line))
|
|
29
|
+
continue;
|
|
30
|
+
seen.add(line);
|
|
31
|
+
lines.push(line);
|
|
17
32
|
}
|
|
18
|
-
return lines
|
|
33
|
+
return lines;
|
|
19
34
|
}
|
|
35
|
+
function section(title, lines, fallback) {
|
|
36
|
+
const body = lines.length > 0 ? lines : [fallback];
|
|
37
|
+
return [title, ...body.map((line) => `- ${line}`)];
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Render the session's events as a structured summary.
|
|
41
|
+
*
|
|
42
|
+
* Sections carry DIFFERENT slices of the session rather than the same lines
|
|
43
|
+
* repeated under every heading: `actions` is what happened in order, `outcomes`
|
|
44
|
+
* is where the session ended up, and `decisions` surfaces the events that
|
|
45
|
+
* recorded a choice (git commits). Repeating one slice under all five headings
|
|
46
|
+
* produced a summary that cost five times the tokens and carried no extra
|
|
47
|
+
* signal — and, once stored, was what every later session got injected with.
|
|
48
|
+
*/
|
|
20
49
|
export function buildStructuredSummary(events, options) {
|
|
21
|
-
const
|
|
50
|
+
const lines = distinctLines(events);
|
|
51
|
+
// Ordered narrative of the session, capped from the front.
|
|
52
|
+
const actions = lines.slice(0, MAX_SECTION_LINES);
|
|
53
|
+
// Where the session ended up: the tail, which is the most recent state.
|
|
54
|
+
const outcomes = lines.length > MAX_SECTION_LINES
|
|
55
|
+
? lines.slice(-Math.min(MAX_SECTION_LINES, lines.length - MAX_SECTION_LINES))
|
|
56
|
+
: lines.slice(-1);
|
|
57
|
+
// Commits are the durable decisions a session leaves behind.
|
|
58
|
+
const decisions = lines
|
|
59
|
+
.filter((line) => /\bgit\s+commit\b/i.test(line))
|
|
60
|
+
.slice(0, MAX_SECTION_LINES);
|
|
22
61
|
const summarySection = [
|
|
23
|
-
"
|
|
24
|
-
...sharedLines.map((line) => `- ${line}`),
|
|
25
|
-
"",
|
|
26
|
-
"actions",
|
|
27
|
-
...sharedLines.map((line) => `- ${line}`),
|
|
28
|
-
"",
|
|
29
|
-
"decisions",
|
|
30
|
-
...sharedLines.map((line) => `- ${line}`),
|
|
62
|
+
...section("actions", actions, "No relevant events captured."),
|
|
31
63
|
"",
|
|
32
|
-
"
|
|
33
|
-
...sharedLines.map((line) => `- ${line}`),
|
|
64
|
+
...section("decisions", decisions, "No explicit decisions recorded."),
|
|
34
65
|
"",
|
|
35
|
-
"outcomes",
|
|
36
|
-
...sharedLines.map((line) => `- ${line}`),
|
|
66
|
+
...section("outcomes", outcomes, "No outcomes captured."),
|
|
37
67
|
].join("\n");
|
|
38
68
|
const facts = events
|
|
39
|
-
.
|
|
40
|
-
.
|
|
69
|
+
.slice(0, MAX_FACT_LINES)
|
|
70
|
+
.map((event) => `- [${event.event_type}] ${extractEventText(event)}`);
|
|
41
71
|
const factSection = ["facts", ...facts].join("\n");
|
|
42
72
|
if (options.factMode === "summary-only") {
|
|
43
73
|
return summarySection;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# Architecture
|
|
2
|
+
|
|
3
|
+
`sessionmem` connects a coding agent's MCP host to a local SQLite memory database. This overview covers its four subsystems and the retrieval and injection flows. It runs locally by default, with settings in `~/.sessionmem/config.json`.
|
|
4
|
+
|
|
5
|
+
## Subsystems
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
┌──────────────────────────────────────────────┐
|
|
9
|
+
│ MCP host │
|
|
10
|
+
│ (Claude Code, Cursor, Codex, Cline, ...) │
|
|
11
|
+
└───────────────────────┬──────────────────────┘
|
|
12
|
+
│ MCP / hooks
|
|
13
|
+
┌───────────▼───────────┐ ┌──────────────┐
|
|
14
|
+
│ adapters │ │ CLI │
|
|
15
|
+
│ (host-specific glue) │ │ (sessionmem) │
|
|
16
|
+
└───────────┬───────────┘ └──────┬───────┘
|
|
17
|
+
│ │
|
|
18
|
+
▼ ▼
|
|
19
|
+
┌──────────────────────────────────────────────┐
|
|
20
|
+
│ core engine │
|
|
21
|
+
│ capture · summarize · retrieval · injection │
|
|
22
|
+
└───────────────────────┬──────────────────────┘
|
|
23
|
+
│
|
|
24
|
+
▼
|
|
25
|
+
┌──────────────────────────────────────────────┐
|
|
26
|
+
│ SQLite storage (local) │
|
|
27
|
+
│ memories · session events · feedback │
|
|
28
|
+
└──────────────────────────────────────────────┘
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### Core engine
|
|
32
|
+
|
|
33
|
+
The core engine captures session events, summarizes them into stored memories at session end, ranks those memories, and selects which to inject. It uses the adapter layer for all communication with specific tools. Retrieval combines semantic similarity, recency, and importance into a relevance score.
|
|
34
|
+
|
|
35
|
+
### Adapters
|
|
36
|
+
|
|
37
|
+
Adapters translate between each MCP host's hooks or runtime and the core engine's API. There are adapters for Claude Code, Cursor, Codex, Cline, Windsurf, Antigravity, QCoder, and generic MCP hosts. They connect host lifecycle events to the engine's capture, summarize, and inject calls. A new platform needs an adapter; the core engine stays the same.
|
|
38
|
+
|
|
39
|
+
### CLI
|
|
40
|
+
|
|
41
|
+
The CLI (`sessionmem`) registers the server with a host and runs the MCP server. It also has commands to search, list, show, forget, export, import, prune, and configure memories. These commands call the same core engine as the adapters.
|
|
42
|
+
|
|
43
|
+
### SQLite storage
|
|
44
|
+
|
|
45
|
+
SQLite stores memories, embeddings for vector search, raw session events, and feedback history in one local file. Database access uses `better-sqlite3`, with forward-only [migrations](migration.md). No hosted database is required.
|
|
46
|
+
|
|
47
|
+
## Retrieval flow
|
|
48
|
+
|
|
49
|
+
When an agent or `sessionmem search` submits a query, the core engine embeds it and runs a vector search over the `memories` table. It ranks candidates by semantic similarity, recency, and importance, then returns the memories with their scores.
|
|
50
|
+
|
|
51
|
+
## Injection flow
|
|
52
|
+
|
|
53
|
+
At session start, the engine retrieves relevant memories and trims them to a token budget, a few hundred tokens by default. It keeps critical warnings and high-importance decisions first, dropping lower-priority content as needed. The adapter adds the result to the host's context. Claude Code's installed SessionStart hook does this automatically. Other hosts need an agent or lifecycle integration to call the corresponding MCP tools; MCP registration alone does not install hooks in those hosts.
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# Benchmark
|
|
2
|
+
|
|
3
|
+
`npm run benchmark` (`scripts/benchmark.mjs`) generates this report using the production functions `retrieveMemories`, `formatStartupInjection`, and `countTokens`. It measures token reduction against full session history and retrieval relevance using curated, synthetic fixtures. It runs offline with a hash-based embedding function, `deterministicEmbed`, and a fixed clock, so repeated runs produce identical output.
|
|
4
|
+
|
|
5
|
+
Startup injection used **~85.6% fewer tokens** than full session history. Retrieval had a **100.0% hit-rate** across 10 curated queries.
|
|
6
|
+
|
|
7
|
+
## Token reduction
|
|
8
|
+
|
|
9
|
+
Token counts use the production `countTokens` function (js-tiktoken `o200k_base`). The table compares a single startup injection and average per-query context with the same full-history baseline. The result above uses startup injection.
|
|
10
|
+
|
|
11
|
+
| Framing | Baseline tokens (full history) | Injected tokens | Reduction |
|
|
12
|
+
|---------|-------------------------------|-----------------|-----------|
|
|
13
|
+
| (a) Top-3 startup injection vs full history | 1587 | 228 | 85.6% |
|
|
14
|
+
| (b) Per-query injected context (avg) vs full history | 1587 | 232 | 85.4% |
|
|
15
|
+
|
|
16
|
+
Row (a) measures one startup injection of the top-ranked memories. Row (b) averages the injected context across the curated queries to model per-turn retrieval.
|
|
17
|
+
|
|
18
|
+
## Retrieval relevance
|
|
19
|
+
|
|
20
|
+
Each query runs through `retrieveMemories` with a limit of 3 results. The returned ids are compared with the expected memory ids. A query counts as a hit if at least one expected memory is returned.
|
|
21
|
+
|
|
22
|
+
- **Hit-rate:** 100.0% (10 queries)
|
|
23
|
+
- **Overall recall:** 100.0%
|
|
24
|
+
- **Overall precision:** 33.3%
|
|
25
|
+
|
|
26
|
+
| Query (memory content) | Expected memory | Recall | Precision |
|
|
27
|
+
|------------------------|-----------------|--------|-----------|
|
|
28
|
+
| We chose JWT access tokens with a 15-minute expiry and ro... | mem-auth-decision | 100.0% | 33.3% |
|
|
29
|
+
| The persistence layer uses better-sqlite3 with WAL mode; ... | mem-db-decision | 100.0% | 33.3% |
|
|
30
|
+
| Embeddings are computed offline with a deterministic sha2... | mem-embed-fact | 100.0% | 33.3% |
|
|
31
|
+
| Retrieval ranks candidates by a weighted blend of semanti... | mem-retrieval-fact | 100.0% | 33.3% |
|
|
32
|
+
| Startup injection formats the top-ranked memories under a... | mem-injection-fact | 100.0% | 33.3% |
|
|
33
|
+
| Never edit an already-shipped migration file in place; ad... | mem-migration-warning | 100.0% | 33.3% |
|
|
34
|
+
| Redaction must run on every memory-write path before stor... | mem-secret-warning | 100.0% | 33.3% |
|
|
35
|
+
| The CLI exposes remember, retention prune, redact-scan, a... | mem-cli-fact | 100.0% | 33.3% |
|
|
36
|
+
| Tests use vitest; integration specs seed an in-memory dat... | mem-testing-fact | 100.0% | 33.3% |
|
|
37
|
+
| The MCP server runs over stdio and exposes memory retriev... | mem-mcp-decision | 100.0% | 33.3% |
|
|
38
|
+
|
|
39
|
+
### Relevance interpretation
|
|
40
|
+
|
|
41
|
+
`deterministicEmbed` uses a hash-based fingerprint. Identical text has a cosine similarity of 1.0; wording changes produce noisy scores. It supports exact or near-exact recall rather than semantic matching of paraphrases. These queries contain the target memory's content, so the hit-rate measures whether ranking retrieves that memory in that case. Matching paraphrases requires a semantic embedding model. Replacing the embedding model leaves the retrieval and scoring code unchanged, so the benchmark can still exercise those paths.
|
|
42
|
+
|
|
43
|
+
## Reproducing
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
npm run build # benchmark imports built functions from dist/
|
|
47
|
+
npm run benchmark # regenerates this file deterministically
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
_Fixtures are synthetic; the report contains only aggregate metrics and synthetic example content. Generated by `scripts/benchmark.mjs`._
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# Cloud Summarization
|
|
2
|
+
|
|
3
|
+
## Default Mode
|
|
4
|
+
|
|
5
|
+
`sessionmem` defaults to local summarization. Local mode runs without external API calls.
|
|
6
|
+
|
|
7
|
+
## How to Enable Cloud Mode
|
|
8
|
+
|
|
9
|
+
Cloud mode activates only when both conditions are true:
|
|
10
|
+
|
|
11
|
+
1. `allowCloudSummarization=true`
|
|
12
|
+
2. `ANTHROPIC_API_KEY` is present (mapped to `anthropicApiKey` in runtime config)
|
|
13
|
+
|
|
14
|
+
If either condition is missing, summarization stays local.
|
|
15
|
+
|
|
16
|
+
## How It Works
|
|
17
|
+
|
|
18
|
+
Cloud summarization runs in two steps:
|
|
19
|
+
|
|
20
|
+
1. **Local preprocessing** - the local summarizer extracts and structures session events
|
|
21
|
+
into a short transcript, with secret redaction applied before it leaves the machine.
|
|
22
|
+
|
|
23
|
+
2. **Cloud compression** - the transcript is sent to the Anthropic Messages API with
|
|
24
|
+
instructions to compress it into a short list of facts, decisions, and context.
|
|
25
|
+
|
|
26
|
+
### Model Configuration
|
|
27
|
+
|
|
28
|
+
The default model is `claude-sonnet-4-6`, defined as `DEFAULT_SUMMARIZER_MODEL` in
|
|
29
|
+
`src/core/config/policyConfig.ts`. Callers can override the model via the `model` field
|
|
30
|
+
in `CloudSummarizeInput`.
|
|
31
|
+
|
|
32
|
+
### Token Limits
|
|
33
|
+
|
|
34
|
+
The API's `max_tokens` parameter is `Math.min(Math.floor((summaryTokenCap ?? 4000) * 1.5), 8192)`.
|
|
35
|
+
The response is then trimmed to `summaryTokenCap`.
|
|
36
|
+
|
|
37
|
+
## Runtime Warning Signals
|
|
38
|
+
|
|
39
|
+
When cloud mode is active, lifecycle responses include:
|
|
40
|
+
|
|
41
|
+
- warning code: `cloud_summarization_enabled`
|
|
42
|
+
- warning message: `Cloud summarization active: allowCloudSummarization=true and ANTHROPIC_API_KEY present`
|
|
43
|
+
|
|
44
|
+
Use these warnings to check whether cloud summarization is active.
|
|
45
|
+
|
|
46
|
+
## Failure Fallback Behavior
|
|
47
|
+
|
|
48
|
+
Cloud summarization retries 2 times. If the retries fail, it falls back to local summarization automatically.
|
|
49
|
+
|
|
50
|
+
Cloud failures are saved in `summarization_failures`. If local fallback succeeds,
|
|
51
|
+
the response includes a warning about the cloud failure. If fallback also fails,
|
|
52
|
+
an additional failure record is saved.
|
|
53
|
+
|
|
54
|
+
## Manual Summarization When Auto Is Disabled
|
|
55
|
+
|
|
56
|
+
Setting `autoSummarize=false` disables automatic `session_end` summarization only.
|
|
57
|
+
You can still call `summarizeSessionToMemory` manually.
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# Migration and Upgrade Policy
|
|
2
|
+
|
|
3
|
+
This guide explains SQLite migrations and what to expect when upgrading sessionmem. Configuration and the default database live under `~/.sessionmem/`.
|
|
4
|
+
|
|
5
|
+
## Migration system
|
|
6
|
+
|
|
7
|
+
`sessionmem` updates its SQLite schema with forward-only migrations. Each migration is a `.sql` file under `src/core/schema/migrations/`. Zero-padded numeric prefixes determine the order:
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
src/core/schema/migrations/
|
|
11
|
+
001_initial.sql
|
|
12
|
+
002_indexes.sql
|
|
13
|
+
003_summarization_failures.sql
|
|
14
|
+
004_memory_feedback.sql
|
|
15
|
+
005_team_provenance.sql
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
### How migrations run
|
|
19
|
+
|
|
20
|
+
At startup, the runner creates the `_migrations` tracking table if needed and sorts the `.sql` files by name. It applies each migration in its own transaction, skipping any already recorded in `_migrations`. Starting sessionmem again does not reapply completed migrations. There are no down-migrations.
|
|
21
|
+
|
|
22
|
+
### Build-time copy: `copy-migrations`
|
|
23
|
+
|
|
24
|
+
The TypeScript compiler does not copy `.sql` files. The build step `copy-migrations` runs `scripts/copy-migrations.mjs` to copy them from `src/core/schema/migrations/` to `dist/core/schema/migrations/`. This includes the migrations in the published package alongside the compiled code.
|
|
25
|
+
|
|
26
|
+
At runtime, sessionmem loads migrations relative to the installed module, using the packaged `dist` copy. It does not search the working directory, where an attacker could place a malicious `migrations/` folder (Phase 5 Decision #15). After adding or editing a migration, re-run the build so `copy-migrations` updates `dist`.
|
|
27
|
+
|
|
28
|
+
### Adding a migration (contributors)
|
|
29
|
+
|
|
30
|
+
1. Add a new `NNN_description.sql` file with the next ordinal in `src/core/schema/migrations/`.
|
|
31
|
+
2. Keep it forward-only and idempotent where practical.
|
|
32
|
+
3. Re-run the build so `copy-migrations` copies it into `dist/core/schema/migrations/`.
|
|
33
|
+
|
|
34
|
+
## Version-upgrade policy
|
|
35
|
+
|
|
36
|
+
`sessionmem` follows **semantic versioning** (semver): `MAJOR.MINOR.PATCH`.
|
|
37
|
+
|
|
38
|
+
- **Patch / minor upgrades** work with your existing database. Additive, forward-only migrations run automatically when the upgraded version first opens it. Stored memories are preserved and no manual migration is needed.
|
|
39
|
+
- **Major-version upgrades** may introduce breaking changes. The release notes will describe behavior changes and any manual migration steps.
|
|
40
|
+
- **Downgrades after migration are unsupported.** Before a major upgrade, run `sessionmem export` to keep a portable JSON copy of your memories that you can re-import if needed.
|
|
41
|
+
|
|
42
|
+
This policy applies to upgrades between published v1 releases. Each release opens existing databases through the same forward-only migration runner.
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
# Privacy and Retention
|
|
2
|
+
|
|
3
|
+
`sessionmem` stores memories locally, redacts secrets before saving, and prunes old memories according to a retention policy. Configure both in `~/.sessionmem/config.json` or with the CLI commands below.
|
|
4
|
+
|
|
5
|
+
## Secret Redaction
|
|
6
|
+
|
|
7
|
+
Redaction replaces common secret patterns with `REDACTED` before saving memory content. It runs during:
|
|
8
|
+
|
|
9
|
+
- automatic summarization at session end,
|
|
10
|
+
- manual memory storage (`storeMemory` or `batchStoreMemory`),
|
|
11
|
+
- import.
|
|
12
|
+
|
|
13
|
+
The `redactionEnabled` flag controls all of these operations and defaults to `true`.
|
|
14
|
+
|
|
15
|
+
### Redacted categories
|
|
16
|
+
|
|
17
|
+
| Category | Examples |
|
|
18
|
+
|----------|----------|
|
|
19
|
+
| Email addresses | `user@example.com` |
|
|
20
|
+
| `sk-` API keys | OpenAI-style `sk-...` keys |
|
|
21
|
+
| AWS access keys | `AKIA...` |
|
|
22
|
+
| GitHub tokens | `ghp_...`, `gho_...`, and related prefixes |
|
|
23
|
+
| Bearer tokens | `Authorization: Bearer ...` |
|
|
24
|
+
| Private key blocks | `-----BEGIN ... PRIVATE KEY-----` private key blocks |
|
|
25
|
+
| Connection-string assignments | `password=...`, `secret=...` |
|
|
26
|
+
| JWTs | three-part `xxxxx.yyyyy.zzzzz` tokens |
|
|
27
|
+
|
|
28
|
+
The project's CI secret scanning checks for similar categories.
|
|
29
|
+
|
|
30
|
+
### Partial-failure warnings
|
|
31
|
+
|
|
32
|
+
If redaction partly fails, the response includes a `redaction_partial_failure` warning through the usual lifecycle warning mechanism. The write still completes. Review the affected memory when you see this warning.
|
|
33
|
+
|
|
34
|
+
### Disabling redaction
|
|
35
|
+
|
|
36
|
+
Setting `redactionEnabled` to `false` disables redaction for all memory writes. Leave it enabled to keep these checks.
|
|
37
|
+
|
|
38
|
+
## Retention Policy
|
|
39
|
+
|
|
40
|
+
Retention pruning limits growth of the local store by deleting memories older than a configured age.
|
|
41
|
+
|
|
42
|
+
- **Scope:** the `memories` table only. Session events and feedback history are never touched.
|
|
43
|
+
- **Age basis:** a memory's `created_at` timestamp.
|
|
44
|
+
- **Default window:** `90` days (`retentionDays`).
|
|
45
|
+
- **Disabling:** set `retentionDays` to `0` to disable pruning. Negative values are rejected by the config schema.
|
|
46
|
+
- **Hard delete:** pruned memories are permanently deleted from SQLite. There is no archive or soft-delete.
|
|
47
|
+
|
|
48
|
+
> Pruning is permanent. Run `sessionmem export` first if you want to keep a copy of the memories.
|
|
49
|
+
|
|
50
|
+
## Automatic vs Manual Pruning
|
|
51
|
+
|
|
52
|
+
**Automatic (session-end):** a lightweight prune check runs alongside summarization without blocking or delaying it.
|
|
53
|
+
|
|
54
|
+
**Manual:** the prune command previews deletions by default. It only deletes memories when you pass `--force`:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
sessionmem retention prune
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Dry-run output:
|
|
61
|
+
|
|
62
|
+
```
|
|
63
|
+
Would delete N memories older than {retentionDays} days. Pass --force to confirm.
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
To delete eligible memories:
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
sessionmem retention prune --force
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Use `--days <n>` to set the retention window for one run, overriding `config.json` and the built-in default:
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
sessionmem retention prune --days 30
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## One-time Redaction Scrub
|
|
79
|
+
|
|
80
|
+
Normal memory writes do not recheck older memories. To check memories saved before redaction coverage was expanded, run `redact-scan`. It previews changes by default:
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
sessionmem redact-scan
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
The preview shows matching memories and a count. To redact and update them in place:
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
sessionmem redact-scan --apply
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
`--apply` rewrites matching memories with `REDACTED` placeholders and prints a summary count.
|
|
93
|
+
|
|
94
|
+
## Policy Config
|
|
95
|
+
|
|
96
|
+
Policy settings live in `~/.sessionmem/config.json`:
|
|
97
|
+
|
|
98
|
+
```json
|
|
99
|
+
{
|
|
100
|
+
"retentionDays": 90,
|
|
101
|
+
"redactionEnabled": true
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### Precedence
|
|
106
|
+
|
|
107
|
+
Settings take precedence in this order, highest first:
|
|
108
|
+
|
|
109
|
+
1. **CLI flag** (e.g. `retention prune --days 30`)
|
|
110
|
+
2. **`config.json`** value
|
|
111
|
+
3. **built-in default**
|
|
112
|
+
|
|
113
|
+
If `config.json` is missing or invalid, sessionmem uses the built-in defaults unless a CLI flag overrides them.
|
|
114
|
+
|
|
115
|
+
### Reading and writing config
|
|
116
|
+
|
|
117
|
+
Use `config get` and `config set` to read and change settings:
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
sessionmem config get retentionDays
|
|
121
|
+
sessionmem config set retention.days 30
|
|
122
|
+
sessionmem config set redactionEnabled false
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
You can use either the dotted key `retention.days` or the field name `retentionDays`. Invalid values, such as a non-integer for `retentionDays` or a value other than `true`/`false` for `redactionEnabled`, produce an error without changing the file.
|
|
126
|
+
|
|
127
|
+
### Install and uninstall behavior
|
|
128
|
+
|
|
129
|
+
- `sessionmem install` creates `config.json` with defaults (`retentionDays: 90`, `redactionEnabled: true`) if the file is missing. It leaves an existing file unchanged.
|
|
130
|
+
- `sessionmem uninstall` keeps `config.json` and the database unless you pass `--purge`.
|
|
131
|
+
|
|
132
|
+
## Stats Visibility
|
|
133
|
+
|
|
134
|
+
`sessionmem stats` shows the current retention and redaction settings, for example:
|
|
135
|
+
|
|
136
|
+
```
|
|
137
|
+
Retention: 90 days (12 memories eligible for pruning)
|
|
138
|
+
Redaction: enabled
|
|
139
|
+
```
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# Team Mode (Shared Memory)
|
|
2
|
+
|
|
3
|
+
Team mode shares `sessionmem` memories through a directory everyone on the team can read and write, such as a network drive or synced folder. With team mode enabled, `sessionmem sync` writes your local memories there and reads teammates' memories into your database. Your agent can then use decisions, warnings, and facts recorded by the team.
|
|
4
|
+
|
|
5
|
+
Team mode is off by default. Enable it to start sharing.
|
|
6
|
+
|
|
7
|
+
## Setup
|
|
8
|
+
|
|
9
|
+
Enable team mode by pointing it at a shared directory:
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
sessionmem team enable <shared-path>
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
`<shared-path>` must be a directory that all teammates can read and write, typically a network share or a folder synced across machines. The path is recorded in `~/.sessionmem/config.json` under the `team` section (`team.enabled` / `team.sharedPath`).
|
|
16
|
+
|
|
17
|
+
Inside the shared path, `sessionmem` uses a per-project, per-user file layout:
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
{sharedPath}/{project_id}/{username}.json
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Each teammate writes one snapshot file named after their OS username. Each project has its own subdirectory to keep snapshots separate.
|
|
24
|
+
|
|
25
|
+
To check the current state:
|
|
26
|
+
|
|
27
|
+
```
|
|
28
|
+
sessionmem team status
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
This reports whether team mode is enabled and whether the shared path is reachable.
|
|
32
|
+
|
|
33
|
+
## Usage: syncing
|
|
34
|
+
|
|
35
|
+
Once team mode is enabled, run:
|
|
36
|
+
|
|
37
|
+
```
|
|
38
|
+
sessionmem sync
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Each `sync` runs two steps:
|
|
42
|
+
|
|
43
|
+
1. **Push**: writes a full snapshot of this project's local memories to
|
|
44
|
+
`{sharedPath}/{project_id}/{username}.json`. The write is atomic (a temp file
|
|
45
|
+
is written in the same directory and then renamed) so teammates don't read a
|
|
46
|
+
partly written snapshot.
|
|
47
|
+
2. **Pull**: reads every other `*.json` snapshot in the project directory and
|
|
48
|
+
merges those memories into your local database.
|
|
49
|
+
|
|
50
|
+
When it finishes, `sync` prints a summary:
|
|
51
|
+
|
|
52
|
+
```
|
|
53
|
+
Pushed N memories, pulled M new + updated K from teammates.
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
`N` is the number of local memories shared, `M` is the number of new teammate memories added, and `K` is the number of existing memories updated from teammate snapshots.
|
|
57
|
+
|
|
58
|
+
If team mode is disabled, `sync` makes no changes and tells you to run `team enable` first.
|
|
59
|
+
|
|
60
|
+
## Provenance and author annotation
|
|
61
|
+
|
|
62
|
+
Each memory records its source:
|
|
63
|
+
|
|
64
|
+
- **`author`**: the OS username of whoever created the memory.
|
|
65
|
+
- **`created_at`**: when it was first created.
|
|
66
|
+
|
|
67
|
+
In the agent's startup context, teammate memories have an `author:` prefix showing their recorded author, for example:
|
|
68
|
+
|
|
69
|
+
```
|
|
70
|
+
- [decision] alice: decided to use X (...)
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Your own memories have no prefix. Older memories with an empty author also have no prefix. The prefix appears when the recorded author differs from your local username.
|
|
74
|
+
|
|
75
|
+
## Conflict behavior
|
|
76
|
+
|
|
77
|
+
When the same memory id exists locally and in a teammate's snapshot, `sync` resolves it as follows:
|
|
78
|
+
|
|
79
|
+
- **Content**: sync uses last-write-wins by id, keeping the most recent content.
|
|
80
|
+
- **Importance**: a teammate's snapshot cannot lower your local copy's importance score.
|
|
81
|
+
- **Cross-project id collisions**: records whose ids already belong to a different project are skipped.
|
|
82
|
+
- **Redaction**: every pulled record is checked again using your secret redaction policy.
|
|
83
|
+
|
|
84
|
+
## Disable and recovery
|
|
85
|
+
|
|
86
|
+
To stop syncing and keep memories already pulled from teammates:
|
|
87
|
+
|
|
88
|
+
```
|
|
89
|
+
sessionmem team disable
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
To also delete teammate-authored memories:
|
|
93
|
+
|
|
94
|
+
```
|
|
95
|
+
sessionmem team disable --remove-team-memories
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
This deletes the teammate-authored memories for this project, leaving only the memories you authored locally.
|
|
99
|
+
|
|
100
|
+
## Trust boundary and security
|
|
101
|
+
|
|
102
|
+
Anyone with write access to the shared path can add memories that your agent may use. Control access through the shared directory's filesystem permissions:
|
|
103
|
+
|
|
104
|
+
- Restrict write access with OS filesystem permissions (ACLs) to the people you intend to share memories with.
|
|
105
|
+
- The `author` field is advisory. A person with write access can change it, so the `author:` prefix does not verify identity.
|
|
106
|
+
- `sessionmem` does not authenticate teammates, sign snapshots, or verify authorship. Enforce any additional access controls through the filesystem or file share.
|
|
107
|
+
|
|
108
|
+
## Failure recovery
|
|
109
|
+
|
|
110
|
+
When `sync` encounters filesystem or snapshot errors:
|
|
111
|
+
|
|
112
|
+
- **Missing or unwritable shared path**: `sync` reports the error to stderr and
|
|
113
|
+
exits with a non-zero status. Your local memories are untouched.
|
|
114
|
+
- **A corrupt or truncated teammate file**: the file is skipped with a warning,
|
|
115
|
+
and the rest of the pull continues.
|
|
116
|
+
- **An invalid record inside an otherwise-valid file**: the record is skipped
|
|
117
|
+
with a warning; the remaining records still merge.
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# Troubleshooting
|
|
2
|
+
|
|
3
|
+
This guide covers install failures, adapter (MCP host) issues, and `better-sqlite3` native-build failures. Configuration lives in `~/.sessionmem/config.json`.
|
|
4
|
+
|
|
5
|
+
## Install failures
|
|
6
|
+
|
|
7
|
+
### `config.json` not written
|
|
8
|
+
|
|
9
|
+
`sessionmem install` creates `~/.sessionmem/config.json` only if the file is missing. It leaves an existing config unchanged. To restore defaults, move or delete the old file and re-run `sessionmem install`. To change individual settings, use `sessionmem config set`.
|
|
10
|
+
|
|
11
|
+
### Adapter not detected
|
|
12
|
+
|
|
13
|
+
`install` registers sessionmem with the MCP host in the current directory. If install reports that it could not find a host, you are likely running it outside a project the host recognizes. `cd` into your project root (where the host's config lives) and re-run `sessionmem install`. Use `sessionmem install --adapter <name>` from a plain terminal (for example `--adapter cline` or `--adapter windsurf`, which cannot be reliably auto-detected). Run `sessionmem ping` afterward to check local database access; it does not probe the host's MCP connection.
|
|
14
|
+
|
|
15
|
+
### Command not found
|
|
16
|
+
|
|
17
|
+
If `sessionmem` is not on your `PATH` after `npm install -g sessionmem`, your npm global bin directory is not on `PATH`. Add npm's global prefix `bin` to your shell `PATH`, or invoke via `npx sessionmem`.
|
|
18
|
+
|
|
19
|
+
## Adapter issues
|
|
20
|
+
|
|
21
|
+
Each MCP host uses its own adapter. If the server starts but memories are missing, check the host's configuration:
|
|
22
|
+
|
|
23
|
+
- **Claude Code**: if memories are not injected at session start, confirm `sessionmem install` was run inside the project and that the MCP server entry exists in the host config. Use `sessionmem ping` to verify local database access, then check MCP connectivity in the host.
|
|
24
|
+
- **Cursor**: Cursor must be pointed at the registered MCP server. Re-run `install` from the project root and restart Cursor so it reloads MCP servers.
|
|
25
|
+
- **Generic MCP host**: for any other MCP-compatible host, ensure the host is configured to launch `sessionmem run` as the MCP server command. If the host starts but no memory appears, check that the working directory matches the project whose memories you expect.
|
|
26
|
+
|
|
27
|
+
If commands hang after the host loads, run `sessionmem ping` to check local database access, then check the host's MCP connection.
|
|
28
|
+
|
|
29
|
+
## "0 sessions" / no session data recorded
|
|
30
|
+
|
|
31
|
+
If `sessionmem savings` reports no session data or `sessionmem stats` shows `sessions: 0`, check the following.
|
|
32
|
+
|
|
33
|
+
### The hooks are not installed
|
|
34
|
+
|
|
35
|
+
Session events are captured by a `PostToolUse` hook that `sessionmem install` registers in `~/.claude/settings.json`. If you installed sessionmem before the auto-ingest hook existed, that hook is missing. Re-run `sessionmem install` (it is idempotent) and restart your editor, then confirm the entry exists:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
sessionmem install
|
|
39
|
+
sessionmem stats # check the session_events counter after a few tool calls
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
To watch the hook work in real time, run your host with `SESSIONMEM_DEBUG=1`; the hook then reports each ingest to stderr, including the project id it wrote to.
|
|
43
|
+
|
|
44
|
+
### Memory is keyed to the repository
|
|
45
|
+
|
|
46
|
+
A project's memories are keyed by its **repository root**, the nearest directory containing `.git`. All subdirectories in one repository share the same memory bucket.
|
|
47
|
+
|
|
48
|
+
Outside a repository, the id falls back to the exact working directory. Two different directories that are not in a repository are two different projects, so running `sessionmem stats` from a different folder than the one your host session used will show an empty store. Either run from inside the repository, or pin the id explicitly:
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
export SESSIONMEM_PROJECT_ID=my-project
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Use `sessionmem browse` to list every project that has memories along with its id; the current project is marked.
|
|
55
|
+
|
|
56
|
+
### Not enough events to summarize
|
|
57
|
+
|
|
58
|
+
`handleSessionEnd` only writes a session summary once at least 3 events were captured. A very short session is skipped by design and reports `not enough session events to summarize`.
|
|
59
|
+
|
|
60
|
+
## `better-sqlite3` native-build failures
|
|
61
|
+
|
|
62
|
+
`sessionmem` stores memories in SQLite via `better-sqlite3`, which is a **native** module compiled for your platform and Node version. Most install-time crashes come from this native build.
|
|
63
|
+
|
|
64
|
+
### node-gyp build errors
|
|
65
|
+
|
|
66
|
+
If `npm install` fails compiling `better-sqlite3` with `node-gyp` errors, your toolchain is incomplete:
|
|
67
|
+
|
|
68
|
+
- **Windows**: install the Visual Studio Build Tools (MSVC C++ workload). `node-gyp` needs an MSVC compiler; without it the native build fails.
|
|
69
|
+
- **macOS**: install the Xcode Command Line Tools (`xcode-select --install`).
|
|
70
|
+
- **Linux**: install `build-essential` (or your distro's equivalent: a C/C++ compiler, `make`, and `python3`).
|
|
71
|
+
|
|
72
|
+
### ABI mismatch (Node 20 vs 22)
|
|
73
|
+
|
|
74
|
+
`better-sqlite3` is compiled against a specific Node ABI. If you switch Node versions (for example from Node 20 to Node 22), a prebuilt or previously compiled binary can fail to load with a module-version / ABI mismatch error. Rebuild the native module against your current Node by reinstalling, or run `npm rebuild better-sqlite3`. If you use a Node version manager, make sure you reinstall after switching versions.
|
|
75
|
+
|
|
76
|
+
### Confirming the build
|
|
77
|
+
|
|
78
|
+
After a successful build, `sessionmem stats` (or any command that touches the database) will run without a native-load error. A native-build failure typically surfaces immediately as a "cannot find module" or "was compiled against a different Node.js version" error the first time the database is opened.
|