@hilbras/remembra 3.7.0 → 3.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/CHANGELOG.md +71 -0
- package/README.md +8 -1
- package/dist/backend.d.ts +12 -0
- package/dist/crypto.d.ts +10 -0
- package/dist/crypto.js +77 -0
- package/dist/crypto.js.map +1 -0
- package/dist/diff.d.ts +15 -0
- package/dist/diff.js +135 -0
- package/dist/diff.js.map +1 -0
- package/dist/errors.d.ts +1 -1
- package/dist/errors.js +1 -0
- package/dist/errors.js.map +1 -1
- package/dist/http.d.ts +3 -0
- package/dist/http.js +25 -0
- package/dist/http.js.map +1 -1
- package/dist/index.js +59 -1
- package/dist/index.js.map +1 -1
- package/dist/llm.d.ts +2 -0
- package/dist/llm.js +3 -0
- package/dist/llm.js.map +1 -1
- package/dist/metrics.js +4 -0
- package/dist/metrics.js.map +1 -1
- package/dist/redact.d.ts +33 -0
- package/dist/redact.js +104 -0
- package/dist/redact.js.map +1 -0
- package/dist/service.d.ts +59 -0
- package/dist/service.js +169 -4
- package/dist/service.js.map +1 -1
- package/dist/store.d.ts +17 -1
- package/dist/store.js +126 -3
- package/dist/store.js.map +1 -1
- package/dist/types.d.ts +74 -0
- package/dist/types.js +30 -0
- package/dist/types.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/docs/architecture.md +52 -3
- package/docs/chatgpt.md +25 -0
- package/docs/clients.md +3 -0
- package/docs/lifecycle.md +10 -3
- package/docs/memory-model.md +42 -3
- package/docs/observability.md +7 -2
- package/docs/security.md +62 -3
- package/docs/tools.md +48 -1
- package/package.json +1 -1
package/dist/types.js
CHANGED
|
@@ -38,6 +38,12 @@ export const storeInputShape = {
|
|
|
38
38
|
.default(3)
|
|
39
39
|
.describe("1=minor, 5=critical (default 3)"),
|
|
40
40
|
source: z.string().optional().describe("Originating session or client"),
|
|
41
|
+
confidence: z
|
|
42
|
+
.number()
|
|
43
|
+
.min(0)
|
|
44
|
+
.max(1)
|
|
45
|
+
.optional()
|
|
46
|
+
.describe("Trust in this claim 0..1 (default 1.0 for explicit stores, 0.7 for digests)"),
|
|
41
47
|
};
|
|
42
48
|
export const StoreInput = z
|
|
43
49
|
.object(storeInputShape)
|
|
@@ -74,6 +80,28 @@ export const forgetInputShape = {
|
|
|
74
80
|
id: z.string().describe("Memory id (from memory_store or memory_list)"),
|
|
75
81
|
};
|
|
76
82
|
export const ForgetInput = z.object(forgetInputShape);
|
|
83
|
+
export const getInputShape = {
|
|
84
|
+
id: z.string().describe("Memory id — returns the memory with related links and backlinks"),
|
|
85
|
+
};
|
|
86
|
+
export const GetInput = z.object(getInputShape);
|
|
87
|
+
export const relateInputShape = {
|
|
88
|
+
id: z.string().describe("Source memory id"),
|
|
89
|
+
related: z
|
|
90
|
+
.array(z.string().min(1))
|
|
91
|
+
.min(1)
|
|
92
|
+
.max(50)
|
|
93
|
+
.describe("Target memory ids to link to / unlink from"),
|
|
94
|
+
action: z
|
|
95
|
+
.enum(["add", "remove"])
|
|
96
|
+
.default("add")
|
|
97
|
+
.describe("add (default) creates links, remove deletes them"),
|
|
98
|
+
};
|
|
99
|
+
export const RelateInput = z.object(relateInputShape);
|
|
100
|
+
export const historyInputShape = {
|
|
101
|
+
id: z.string().describe("Memory id to show version history for"),
|
|
102
|
+
limit: z.number().int().min(1).max(100).optional().describe("Max past versions to return (newest first)"),
|
|
103
|
+
};
|
|
104
|
+
export const HistoryInput = z.object(historyInputShape);
|
|
77
105
|
/** Backup file envelope (remembra export / import). */
|
|
78
106
|
export const SNAPSHOT_FORMAT = "remembra-export";
|
|
79
107
|
export const SnapshotInput = z.object({
|
|
@@ -94,6 +122,8 @@ export const SnapshotInput = z.object({
|
|
|
94
122
|
lastSeen: z.string().optional(),
|
|
95
123
|
archivedAt: z.string().optional(),
|
|
96
124
|
provenance: z.enum(["explicit", "auto"]).optional(),
|
|
125
|
+
confidence: z.number().min(0).max(1).optional(),
|
|
126
|
+
related: z.array(z.string()).optional(),
|
|
97
127
|
embedding: z.array(z.number()).optional(),
|
|
98
128
|
}))
|
|
99
129
|
.max(100_000),
|
package/dist/types.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,6CAA6C;AAC7C,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,SAAS,CAAC,CAAC,CAAC;AAG1E;;;GAGG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/dist/version.d.ts
CHANGED
package/dist/version.js
CHANGED
package/docs/architecture.md
CHANGED
|
@@ -21,6 +21,7 @@ How Remembra is put together, and where the extension seams are.
|
|
|
21
21
|
┌──────────────────────────────────┐
|
|
22
22
|
│ MemoryStore (store.ts) │ Plain markdown files + frontmatter
|
|
23
23
|
│ .remembra lock · crash recovery │ (no database, by decision — Q5-A)
|
|
24
|
+
│ history snapshots · AES-GCM at │ (encryption + history opt-in, Phase 8)
|
|
24
25
|
└──────────────────────────────────┘
|
|
25
26
|
```
|
|
26
27
|
|
|
@@ -84,14 +85,20 @@ Anything it fixes is logged:
|
|
|
84
85
|
|
|
85
86
|
All actionable failures are `RemembraError` with a stable `code`
|
|
86
87
|
(`src/errors.ts`): `INVALID_INPUT`, `SNAPSHOT_INVALID`, `SCOPE_ESCAPES_ROOT`,
|
|
87
|
-
`NOT_FOUND`, `CONFLICT`, `LOCK_TIMEOUT`, `IO_ERROR`, `LLM_ERROR
|
|
88
|
+
`NOT_FOUND`, `CONFLICT`, `LOCK_TIMEOUT`, `IO_ERROR`, `LLM_ERROR`,
|
|
89
|
+
`ENCRYPTED_NO_KEY`.
|
|
88
90
|
|
|
89
|
-
- **HTTP** maps codes → statuses (400/404/409/423/500/502) and returns
|
|
91
|
+
- **HTTP** maps codes → statuses (400/404/409/423/500/502/503) and returns
|
|
90
92
|
`{ error, code }` bodies;
|
|
91
93
|
- **MCP tools** return `[CODE] message` text with `isError: true`;
|
|
92
94
|
- raw filesystem failures are wrapped as `IO_ERROR`; Zod failures crossing a
|
|
93
95
|
service boundary become `INVALID_INPUT`/`SNAPSHOT_INVALID` with a field
|
|
94
|
-
summary
|
|
96
|
+
summary;
|
|
97
|
+
- `ENCRYPTED_NO_KEY` (Phase 8) deliberately breaks the "skip unparseable
|
|
98
|
+
files" rule: an encrypted file without `REMEMBRA_ENCRYPT_KEY` (or with a
|
|
99
|
+
wrong one) is **data you must be able to read**, so it propagates —
|
|
100
|
+
searches fail with 503 and `/health` reports
|
|
101
|
+
`storage: "ENCRYPTED_NO_KEY"` instead of silently serving partial results.
|
|
95
102
|
|
|
96
103
|
## Schema versioning
|
|
97
104
|
|
|
@@ -100,6 +107,48 @@ Every memory file carries `version: <n>` in frontmatter (`SCHEMA_VERSION` in
|
|
|
100
107
|
format: bump the constant, add a migration branch in `parse()`, and cover it
|
|
101
108
|
with a fixture test.
|
|
102
109
|
|
|
110
|
+
## Encryption at rest (audit Phase 8, opt-in — `src/crypto.ts`)
|
|
111
|
+
|
|
112
|
+
`REMEMBRA_ENCRYPT_KEY` (64 hex chars, a 32-byte symmetric key used directly —
|
|
113
|
+
no KDF for high-entropy material) flips two chokepoints in `MemoryStore`:
|
|
114
|
+
|
|
115
|
+
- **write** (`writeCached`): serialized bytes → `encryptBuffer` → atomic
|
|
116
|
+
rename. One random 12-byte nonce per write, AES-256-GCM.
|
|
117
|
+
- **read** (`parse`): bytes → `decryptBuffer` → frontmatter parse. Detection
|
|
118
|
+
is by the `RMBENC1` magic prefix, so **plain and encrypted files coexist**
|
|
119
|
+
— mixed trees read fine, cache validation stays stat-based, and history
|
|
120
|
+
snapshots (raw byte copies) keep whatever form they were written in.
|
|
121
|
+
|
|
122
|
+
Migration is an explicit locked operation, not a lazy rewrite:
|
|
123
|
+
`remembra encrypt` / `remembra decrypt` walk `global/`, `scopes/`,
|
|
124
|
+
`archived/` and `.history/`, convert in place under the advisory lock, and
|
|
125
|
+
skip files already in the target state (idempotent). Writes after enabling
|
|
126
|
+
the key are encrypted immediately — migration only covers what's already on
|
|
127
|
+
disk.
|
|
128
|
+
|
|
129
|
+
Failure semantics: `decryptBuffer` passes plain bytes through untouched;
|
|
130
|
+
ciphertext without/with a wrong key throws `ENCRYPTED_NO_KEY` (503). The
|
|
131
|
+
warn-once "skip malformed file" path explicitly re-throws `RemembraError`s —
|
|
132
|
+
unreadable storage must never degrade into *smaller* search results.
|
|
133
|
+
|
|
134
|
+
## Version history (audit Phase 8 — `snapshotHistory` / `history`)
|
|
135
|
+
|
|
136
|
+
`MemoryStore.update()` is the single content-mutation chokepoint (merge
|
|
137
|
+
today; anything future). Before rewriting a file whose **content differs**
|
|
138
|
+
from what's on disk, it copies the raw pre-image to
|
|
139
|
+
`.history/<id>/<epochMs>-<seq>.md`:
|
|
140
|
+
|
|
141
|
+
- `epochMs-seq` names sort lexicographically = chronologically (seq breaks
|
|
142
|
+
same-millisecond ties; the advisory lock serializes writers);
|
|
143
|
+
- raw copy ⇒ encrypted files stay encrypted, plain stay plain, byte-for-byte;
|
|
144
|
+
- content-equality gate ⇒ embedding backfills and `memory_relate` never
|
|
145
|
+
create snapshots;
|
|
146
|
+
- pruning keeps the newest `REMEMBRA_HISTORY_LIMIT` (default 20, `0`
|
|
147
|
+
disables) per id;
|
|
148
|
+
- `.history/` is *data-adjacent but never walked by `all()`* — reads go
|
|
149
|
+
through the explicit `history(id)` path only (`.tmp` cleanup in crash
|
|
150
|
+
recovery does cover it).
|
|
151
|
+
|
|
103
152
|
## Parse cache (audit Phase 5: LRU + lazy loading)
|
|
104
153
|
|
|
105
154
|
Every read goes through an LRU keyed by file path and **validated by
|
package/docs/chatgpt.md
CHANGED
|
@@ -83,6 +83,31 @@ curl -X POST http://localhost:8787/memories/digest \
|
|
|
83
83
|
|
|
84
84
|
Requires `REMEMBRA_LLM` + key — see [providers.md](providers.md).
|
|
85
85
|
|
|
86
|
+
### Get one memory (with links)
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
curl http://localhost:8787/memories/<id> \
|
|
90
|
+
-H "x-api-key: $REMEMBRA_API_KEY"
|
|
91
|
+
# → { "memory": {...}, "related": [...], "backlinks": [...] }
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### Link two memories (relationship graph)
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
curl -X POST http://localhost:8787/memories/<id>/relate \
|
|
98
|
+
-H "content-type: application/json" \
|
|
99
|
+
-H "x-api-key: $REMEMBRA_API_KEY" \
|
|
100
|
+
-d '{"related": ["<other-id>"], "action": "add"}'
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### Version history with diffs
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
curl "http://localhost:8787/memories/<id>/history?limit=5" \
|
|
107
|
+
-H "x-api-key: $REMEMBRA_API_KEY"
|
|
108
|
+
# → { "id": "...", "versions": [{ "content": "...", "diff": "--- ..." }, ...] }
|
|
109
|
+
```
|
|
110
|
+
|
|
86
111
|
## 3. Create the Custom GPT
|
|
87
112
|
|
|
88
113
|
1. Go to **chatgpt.com → Explore GPTs → Create a GPT**.
|
package/docs/clients.md
CHANGED
|
@@ -95,6 +95,9 @@ REMEMBRA_API_KEY="your-secret" remembra --http
|
|
|
95
95
|
| `REMEMBRA_LOCK_TIMEOUT_MS` | `5000` | Max wait for the cross-process storage lock |
|
|
96
96
|
| `REMEMBRA_LOCK_STALE_MS` | `10000` | Age after which a lock with a dead/unknown pid is stolen |
|
|
97
97
|
| `REMEMBRA_CACHE_SIZE` | `10000` | Parse-cache LRU capacity (entries); `0` disables caching |
|
|
98
|
+
| `REMEMBRA_HISTORY_LIMIT` | `20` | Max version snapshots kept per memory; `0` disables history |
|
|
99
|
+
| `REMEMBRA_REDACT` | *(unset)* | `1` enables PII redaction at ingest (irreversible) — see [security.md](security.md#pii-redaction-opt-in-380) |
|
|
100
|
+
| `REMEMBRA_ENCRYPT_KEY` | *(unset)* | 64-hex 32-byte key → AES-256-GCM at rest; run `remembra encrypt` — see [security.md](security.md#encryption-at-rest-opt-in-380) |
|
|
98
101
|
| `REMEMBRA_DEBUG` | *(unset)* | `1` logs the storage root path at startup (off by default: log hygiene) |
|
|
99
102
|
| `REMEMBRA_LOG` | *(auto)* | Force log format: `json` or `text`. Unset → auto: JSON when stderr is piped, text on a TTY. See [observability.md](observability.md) |
|
|
100
103
|
|
package/docs/lifecycle.md
CHANGED
|
@@ -75,6 +75,12 @@ same type + scope) — the LLM is only called when two memories are plausibly
|
|
|
75
75
|
about the same thing. If the merge LLM fails, the item is **stored fresh**
|
|
76
76
|
(fail-open: extraction never loses data).
|
|
77
77
|
|
|
78
|
+
Since 3.8.0 a merge also snapshots the **pre-merge file** into
|
|
79
|
+
`.history/<id>/` first — every past version stays recoverable with
|
|
80
|
+
`memory_history` / `GET /memories/:id/history`, which renders a unified line
|
|
81
|
+
diff of old → new for each version (pruned to `REMEMBRA_HISTORY_LIMIT`,
|
|
82
|
+
default 20).
|
|
83
|
+
|
|
78
84
|
## Configuration
|
|
79
85
|
|
|
80
86
|
| Variable | Default | Purpose |
|
|
@@ -88,7 +94,8 @@ about the same thing. If the merge LLM fails, the item is **stored fresh**
|
|
|
88
94
|
~/.remembra/
|
|
89
95
|
├── global/<id>.md # active, global
|
|
90
96
|
├── scopes/<scope>/<id>.md # active, project-scoped
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
97
|
+
├── archived/
|
|
98
|
+
│ ├── global/<id>.md # archived (excluded from search)
|
|
99
|
+
│ └── scopes/<scope>/<id>.md
|
|
100
|
+
└── .history/<id>/<epoch>-<seq>.md # superseded pre-images (3.8.0)
|
|
94
101
|
```
|
package/docs/memory-model.md
CHANGED
|
@@ -69,9 +69,39 @@ in `/repo/b`. Global memories are always visible.
|
|
|
69
69
|
| `importance` | 1–5 | defaults to 3; higher ranks higher (same weight in both modes) |
|
|
70
70
|
| `source` | string | originating session/client (optional) |
|
|
71
71
|
| `provenance` | `explicit \| auto` | set automatically: `explicit` = stored deliberately, `auto` = digest-extracted; pre-3.4.0 files are neutral |
|
|
72
|
+
| `confidence` | 0–1 | trust in this claim (3.8.0): explicit stores default `1.0`, digests `0.7` (the extraction LLM may supply its own). **Displayed, not ranked** — importance answers "relevant?", confidence answers "true?"; preserved through merge/import/export |
|
|
73
|
+
| `related` | string[] | ids of linked memories (3.8.0) — see [Relationships](#relationships-380) |
|
|
72
74
|
| `id` | 12-char id | assigned automatically (collision-safe) |
|
|
73
75
|
| `createdAt` / `updatedAt` | ISO timestamps | assigned automatically |
|
|
74
76
|
|
|
77
|
+
## Relationships (3.8.0)
|
|
78
|
+
|
|
79
|
+
Memories can link to each other — a directed `related: [ids]` list in
|
|
80
|
+
frontmatter, managed with `memory_relate`:
|
|
81
|
+
|
|
82
|
+
```markdown
|
|
83
|
+
related: [7133edba, 9f2e01aa]
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
- Edges are **stored on the source only**; backlinks are derived at read time
|
|
87
|
+
(`memory_get` / `GET /memories/:id` return both directions), so a link is a
|
|
88
|
+
single write with no consistency dance.
|
|
89
|
+
- Targets are validated on `add` (they must exist); self-links are rejected.
|
|
90
|
+
- Links do not affect ranking — retrieval is unchanged; they are structure for
|
|
91
|
+
the consumer to follow (decision → facts it rests on, history → decision it
|
|
92
|
+
records).
|
|
93
|
+
|
|
94
|
+
## Version history (3.8.0)
|
|
95
|
+
|
|
96
|
+
Any content-changing update (today: contradiction merges) first snapshots the
|
|
97
|
+
on-disk pre-image into `~/.remembra/.history/<id>/<epoch>-<seq>.md` — a
|
|
98
|
+
byte-for-byte copy of the file as it was. `memory_history` /
|
|
99
|
+
`GET /memories/:id/history` return the versions newest-first, each with a
|
|
100
|
+
unified line diff against its predecessor. Snapshots prune to
|
|
101
|
+
`REMEMBRA_HISTORY_LIMIT` (default 20, `0` disables); embedding backfills and
|
|
102
|
+
linking never snapshot (content unchanged). History files are invisible to
|
|
103
|
+
`all()`/search — `.history` is never walked as data.
|
|
104
|
+
|
|
75
105
|
## Retrieval ranking
|
|
76
106
|
|
|
77
107
|
When `memory_search` runs, memories are scored in layers:
|
|
@@ -131,7 +161,16 @@ Layout:
|
|
|
131
161
|
$REMEMBRA_HOME/ # defaults to ~/.remembra
|
|
132
162
|
├── global/
|
|
133
163
|
│ └── <id>.md
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
164
|
+
├── scopes/
|
|
165
|
+
│ └── <scope>/
|
|
166
|
+
│ └── <id>.md
|
|
167
|
+
├── archived/ # same shape, out of search (lifecycle.md)
|
|
168
|
+
└── .history/<id>/ # superseded pre-images (version history, 3.8.0)
|
|
137
169
|
```
|
|
170
|
+
|
|
171
|
+
> Opt-in: with `REMEMBRA_ENCRYPT_KEY` set, every file above is written as
|
|
172
|
+
> AES-256-GCM ciphertext instead (same names, detected by magic bytes) —
|
|
173
|
+
> see [security.md](security.md#encryption-at-rest-opt-in-380). With
|
|
174
|
+
> `REMEMBRA_REDACT=1`, PII patterns are replaced with typed placeholders
|
|
175
|
+
> (`<EMAIL>`, `<CARD>`, …) *before* this file is ever written — see
|
|
176
|
+
> [security.md](security.md#pii-redaction-opt-in-380).
|
package/docs/observability.md
CHANGED
|
@@ -25,7 +25,8 @@ Example JSON events:
|
|
|
25
25
|
|
|
26
26
|
Event names in the wild: `http_listening`, `mcp_listening`, `search`,
|
|
27
27
|
`shutdown`, `crash_recovery`, `memory_parse_skipped`, `embedding_failed`,
|
|
28
|
-
`touch_failed`, `merge_llm_failed`, `decay_failed
|
|
28
|
+
`touch_failed`, `merge_llm_failed`, `decay_failed`, `redacted` (3.8.0 — PII
|
|
29
|
+
found at ingest; fields are per-kind counts, never the matched text).
|
|
29
30
|
|
|
30
31
|
## Metrics — `GET /metrics`
|
|
31
32
|
|
|
@@ -38,7 +39,7 @@ stays exempt so unauthenticated readiness probes keep working.
|
|
|
38
39
|
|
|
39
40
|
| Series | Type | Labels | Meaning |
|
|
40
41
|
|---|---|---|---|
|
|
41
|
-
| `remembra_http_requests_total` | counter | `route`, `method`, `status` | Requests. `route` is a fixed low-cardinality label (`health`/`metrics`/`memories`/`search`/`digest`/`maintain`/`memory_item`/`other`) — never the raw path. |
|
|
42
|
+
| `remembra_http_requests_total` | counter | `route`, `method`, `status` | Requests. `route` is a fixed low-cardinality label (`health`/`metrics`/`memories`/`search`/`digest`/`maintain`/`memory_item`/`memory_sub`/`other`) — never the raw path. `memory_sub` = the `relate`/`history` sub-routes (3.8.0). |
|
|
42
43
|
| `remembra_http_request_duration_seconds` | histogram | `route` | Request latency. |
|
|
43
44
|
| `remembra_errors_total` | counter | `code`, `transport` | Classified errors (`http`/`mcp`). Codes: the [error codes](architecture.md#error-classification-audit-phase-2) plus `INVALID_INPUT`, `PAYLOAD_TOO_LARGE`, `INTERNAL`. |
|
|
44
45
|
| `remembra_searches_total` | counter | — | `memory_search` invocations. |
|
|
@@ -49,6 +50,10 @@ stays exempt so unauthenticated readiness probes keep working.
|
|
|
49
50
|
| `remembra_digest_duration_seconds` | histogram | — | Digest run latency (includes lock queueing). |
|
|
50
51
|
| `remembra_cache_events_total` | counter | `result` | Parse-cache probes: `hit` / `miss`. |
|
|
51
52
|
| `remembra_cache_entries` | gauge | — | Parse-cache entries currently held. |
|
|
53
|
+
| `remembra_redactions_total` | counter | `kind` | PII placeholders written at ingest (3.8.0): `email`/`ssn`/`card`/`phone`/`secret`. Zero (absent) unless `REMEMBRA_REDACT=1`. |
|
|
54
|
+
| `remembra_relate_total` | counter | `action` | `memory_relate` link writes (3.8.0): `add` / `remove` (no-op idempotent calls don't count). |
|
|
55
|
+
| `remembra_history_snapshots_total` | counter | — | History pre-images written (3.8.0). Growth rate ≈ content-changing updates. |
|
|
56
|
+
| `remembra_encryption_migrations_total` | counter | `mode` | `remembra encrypt`/`decrypt` files converted (3.8.0). |
|
|
52
57
|
| `remembra_info` | gauge | `version` | Build info, always `1`. |
|
|
53
58
|
|
|
54
59
|
### Scrape config
|
package/docs/security.md
CHANGED
|
@@ -61,6 +61,60 @@ Retrieved memories should be treated as **data with provenance**, not commands
|
|
|
61
61
|
| **ID collisions** | 12-hex IDs (2⁴⁸) + existence check on store |
|
|
62
62
|
| **Content-Length** | Set on every response |
|
|
63
63
|
| **Metrics auth (3.7.0)** | `GET /metrics` sits *after* the API-key check — counters and latencies never leak without the key (`/health` stays exempt for readiness probes) |
|
|
64
|
+
| **PII redaction (3.8.0, opt-in)** | `REMEMBRA_REDACT=1` strips emails, Luhn-valid card numbers, SSNs, phone numbers and high-entropy secrets at the *ingest layer* (`memory_store`, digest items, merge output) — raw patterns never reach disk, embeddings, or export snapshots |
|
|
65
|
+
| **Encryption at rest (3.8.0, opt-in)** | `REMEMBRA_ENCRYPT_KEY` → AES-256-GCM per file; reading an encrypted file without the key fails **loudly** (`ENCRYPTED_NO_KEY`, HTTP 503, `/health` 503) — never warn-skipped as if the data didn't exist |
|
|
66
|
+
|
|
67
|
+
## Encryption at rest (opt-in, 3.8.0)
|
|
68
|
+
|
|
69
|
+
Plain markdown stays the default (you can read and edit your memories —
|
|
70
|
+
that's the point). Setting a key flips storage to ciphertext:
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
export REMEMBRA_ENCRYPT_KEY="$(node -p 'require("node:crypto").randomBytes(32).toString("hex")')"
|
|
74
|
+
remembra encrypt # migrate the existing tree (memories + history) in place
|
|
75
|
+
remembra --http # from here on, writes are AES-256-GCM
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
| | |
|
|
79
|
+
|---|---|
|
|
80
|
+
| **Format** | `RMBENC1 │ nonce(12) │ tag(16) │ ciphertext` per file — AES-256-GCM via `node:crypto`, zero dependencies, random nonce per write, same `.md` names (detected by magic bytes) |
|
|
81
|
+
| **Key** | 64 hex chars (32 bytes) used directly — no KDF needed for a high-entropy symmetric key. *Not* a human passphrase |
|
|
82
|
+
| **Mixed trees** | Plain files stay readable while the key is set, and `remembra decrypt` reverses the migration — both directions are idempotent |
|
|
83
|
+
| **Fail-loud** | Encrypted file + missing/wrong key → `ENCRYPTED_NO_KEY` (HTTP 503, MCP `[ENCRYPTED_NO_KEY]`, `/health` 503). GCM auth failure makes a wrong key indistinguishable from tampering |
|
|
84
|
+
| **What it protects** | At-rest exfiltration: stolen backups, copied `~/.remembra`, a leaked git history of the directory |
|
|
85
|
+
| **What it does not** | A runtime attacker on your machine can read the env of the process holding the key — this is not a substitute for OS disk encryption & process isolation; and files stop being human-readable (decrypt first: unset the key after `remembra decrypt`) |
|
|
86
|
+
|
|
87
|
+
Export snapshots (`remembra export`) contain **decrypted** JSON — they are
|
|
88
|
+
protected by file permissions like any other backup.
|
|
89
|
+
|
|
90
|
+
## PII redaction (opt-in, 3.8.0)
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
export REMEMBRA_REDACT=1
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Everything that enters storage — `memory_store` calls, every digest-extracted
|
|
97
|
+
item, and merge output — runs through a pattern filter first:
|
|
98
|
+
|
|
99
|
+
| Matches | Placeholder | Guard against false positives |
|
|
100
|
+
|---------|-------------|------------------------------|
|
|
101
|
+
| Emails | `<EMAIL>` | — |
|
|
102
|
+
| Card numbers (13–19 digits) | `<CARD>` | must pass the **Luhn** check |
|
|
103
|
+
| SSNs (`123-45-6789`) | `<SSN>` | dashed format only |
|
|
104
|
+
| Phone numbers | `<PHONE>` | separators required, 10–15 digits — dates (`2026-09-23`, 8 digits) and versions (`3.6.0`) never match |
|
|
105
|
+
| Provider tokens (`sk-…`, `ghp_…`, `AKIA…`) + ≥40-char high-entropy blobs | `<SECRET>` | generic blobs must contain a digit (long English words survive) |
|
|
106
|
+
|
|
107
|
+
Properties:
|
|
108
|
+
|
|
109
|
+
- **Irreversible by design** — the original bytes are not kept anywhere;
|
|
110
|
+
before enabling, assume anything redacted is gone from future exports too.
|
|
111
|
+
- **Not a compliance control** — regex covers the common shapes; names,
|
|
112
|
+
addresses in free prose, and anything the patterns miss are untouched.
|
|
113
|
+
Treat it as belt-and-braces on top of not feeding PII to your LLM providers.
|
|
114
|
+
- The **extraction LLM still sees the raw transcript** (it must, to
|
|
115
|
+
understand it) — redaction guards what Remembra *stores*, not what your
|
|
116
|
+
`REMEMBRA_LLM` provider receives. Use `memory_store` (not digest) for
|
|
117
|
+
content you must not send to a third-party model.
|
|
64
118
|
|
|
65
119
|
## Deployment checklist
|
|
66
120
|
|
|
@@ -82,17 +136,22 @@ remembra --http
|
|
|
82
136
|
directory)
|
|
83
137
|
- [ ] Periodic `memory_list {type: "role"}` audit
|
|
84
138
|
- [ ] LLM/embedding keys scoped to least privilege
|
|
139
|
+
- [ ] Consider `REMEMBRA_REDACT=1` before storing content derived from other
|
|
140
|
+
people's data (redaction is irreversible — decide once, up front)
|
|
141
|
+
- [ ] Consider `REMEMBRA_ENCRYPT_KEY` when the store leaves your machine
|
|
142
|
+
(backups, shared filesystems) — generate 32 random bytes, store the key
|
|
143
|
+
in your secret manager, run `remembra encrypt`
|
|
85
144
|
|
|
86
145
|
## Known non-goals (current version)
|
|
87
146
|
|
|
88
147
|
- **No multi-tenancy** — one store per installation; scope isolates *projects*,
|
|
89
148
|
not *users*. Never share one instance between mutually untrusting users.
|
|
90
|
-
- **No encryption at rest** — files are plaintext markdown (by design: you can
|
|
91
|
-
read and edit them). Use filesystem-level encryption if needed.
|
|
92
|
-
- **No PII redaction** — what you store is what's written to disk.
|
|
93
149
|
- **Single-writer assumption per store, now cross-process safe** — mutations
|
|
94
150
|
take an advisory lockfile (`O_EXCL`, stale-steal, typed `LOCK_TIMEOUT`), so
|
|
95
151
|
an MCP server, the `remembra maintain` CLI, and a session digest can run
|
|
96
152
|
against one store concurrently on one machine. Network filesystems with
|
|
97
153
|
unreliable `O_EXCL` semantics are untested; `remembra export` for backups
|
|
98
154
|
across machines.
|
|
155
|
+
- Encryption-at-rest and PII redaction are **off by default** (both since
|
|
156
|
+
3.8.0, both opt-in above) — defaults keep files human-readable and
|
|
157
|
+
byte-faithful to what you stored.
|
package/docs/tools.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Tool Reference
|
|
2
2
|
|
|
3
|
-
Remembra exposes
|
|
3
|
+
Remembra exposes nine MCP tools. All of them work the same way across every
|
|
4
4
|
MCP-compatible client.
|
|
5
5
|
|
|
6
6
|
## `memory_store`
|
|
@@ -15,6 +15,7 @@ Persist a memory so it survives context-window resets.
|
|
|
15
15
|
| `tags` | string[] | no | `[]` | Keywords that boost retrieval |
|
|
16
16
|
| `importance` | 1–5 | no | `3` | Ranking weight |
|
|
17
17
|
| `source` | string | no | — | Originating session/client |
|
|
18
|
+
| `confidence` | 0–1 | no | auto | Trust in this claim — defaults to `1.0` for explicit stores, `0.7` for digest extractions (the LLM may supply its own) |
|
|
18
19
|
|
|
19
20
|
**When to use which type:**
|
|
20
21
|
- Something the model should *know* → `fact`
|
|
@@ -64,6 +65,50 @@ Permanently delete a memory.
|
|
|
64
65
|
|
|
65
66
|
Returns an error result if no memory matches the id.
|
|
66
67
|
|
|
68
|
+
## `memory_get`
|
|
69
|
+
|
|
70
|
+
Fetch one memory by id with its full content, its `related` links resolved,
|
|
71
|
+
and its **backlinks** (memories that point at it). Use after `memory_search`
|
|
72
|
+
when you need the whole statement, not the snippet.
|
|
73
|
+
|
|
74
|
+
| Argument | Type | Required | Description |
|
|
75
|
+
|----------|------|----------|-------------|
|
|
76
|
+
| `id` | string | ✅ | Memory id |
|
|
77
|
+
|
|
78
|
+
Returns `[NOT_FOUND] …` with `isError: true` if no memory matches.
|
|
79
|
+
|
|
80
|
+
## `memory_relate`
|
|
81
|
+
|
|
82
|
+
Create or remove directed links between memories — the relationship graph.
|
|
83
|
+
Tie a decision to the facts it depends on, or a history entry to the decision
|
|
84
|
+
it records. Targets must exist; backlinks are derived at read time, so one
|
|
85
|
+
write keeps the edge consistent. See [memory-model.md](memory-model.md#relationships-380).
|
|
86
|
+
|
|
87
|
+
| Argument | Type | Required | Default | Description |
|
|
88
|
+
|----------|------|----------|---------|-------------|
|
|
89
|
+
| `id` | string | ✅ | — | Source memory id |
|
|
90
|
+
| `related` | string[] | ✅ | — | Target memory ids (1–50; must exist on `add`) |
|
|
91
|
+
| `action` | `add \| remove` | no | `add` | Create or delete the links |
|
|
92
|
+
|
|
93
|
+
Idempotent: re-linking what's already linked is a no-op (no `updatedAt` churn).
|
|
94
|
+
A memory cannot be related to itself (`INVALID_INPUT`).
|
|
95
|
+
|
|
96
|
+
## `memory_history`
|
|
97
|
+
|
|
98
|
+
Version history of one memory with unified line diffs — every
|
|
99
|
+
content-changing update (e.g. a contradiction merge) snapshots the previous
|
|
100
|
+
version first. Newest version first; each entry diffs against its
|
|
101
|
+
predecessor. See [lifecycle.md](lifecycle.md#contradiction-merging).
|
|
102
|
+
|
|
103
|
+
| Argument | Type | Required | Description |
|
|
104
|
+
|----------|------|----------|-------------|
|
|
105
|
+
| `id` | string | ✅ | Memory id |
|
|
106
|
+
| `limit` | 1–100 | no | Max *past* versions to return |
|
|
107
|
+
|
|
108
|
+
Snapshots live in `~/.remembra/.history/<id>/`, pruned to
|
|
109
|
+
`REMEMBRA_HISTORY_LIMIT` (default 20) per memory; `0` disables history.
|
|
110
|
+
Non-content updates (embedding backfills, linking) never snapshot.
|
|
111
|
+
|
|
67
112
|
## `memory_digest`
|
|
68
113
|
|
|
69
114
|
Extract memories from a conversation transcript using the configured LLM and
|
|
@@ -94,6 +139,8 @@ Returns counts + affected ids. Also available as `POST /maintain` and the
|
|
|
94
139
|
remembra maintain # decay sweep + vector backfill (one-shot, prints JSON)
|
|
95
140
|
remembra export <file>.json # full backup snapshot incl. archived memories
|
|
96
141
|
remembra import <file>.json # restore; validates whole file first (atomic), idempotent
|
|
142
|
+
remembra encrypt # migrate the whole tree to ciphertext at rest (needs REMEMBRA_ENCRYPT_KEY)
|
|
143
|
+
remembra decrypt # migrate back to plain markdown (also needs the key)
|
|
97
144
|
```
|
|
98
145
|
|
|
99
146
|
Import skips existing ids and exact-duplicate contents, so running it twice —
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hilbras/remembra",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.8.0",
|
|
4
4
|
"description": "External memory for AI assistants — remember facts, decisions, roles and history across sessions. MCP server for OpenCode, Claude Code, Cline, Kimi Code and more.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|