@pyxmate/memory 0.20.4 → 0.21.1

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.
@@ -1,274 +0,0 @@
1
- # Advanced Features
2
-
3
- ## RAG Strategies
4
-
5
- | Strategy | How It Works | Requirements |
6
- |----------|-------------|--------------|
7
- | `'naive'` | Embed query → vector similarity → top-K | (default, always available) |
8
- | `'graph'` | Extract entities → graph traversal → context expansion | `graphStore` in MemoryOptions |
9
- | `'hybrid'` | BM25 + vector + graph + community summaries → RRF fusion (k=60) → reranking | `graphStore` in MemoryOptions |
10
- | `'agentic'` | LLM decides strategy → iterative refinement (3 rounds) | `reasoningProvider` in MemoryOptions |
11
-
12
- ```typescript
13
- // Naive (default)
14
- await memory.search({ query: 'user preferences', limit: 10 });
15
-
16
- // Graph
17
- await memory.search({ query: 'who works at Acme', strategy: 'graph', limit: 10 });
18
-
19
- // Hybrid (recommended for best quality)
20
- await memory.search({ query: 'deployment config', strategy: 'hybrid', limit: 10 });
21
-
22
- // With query transformation (HyDE generates hypothetical answer, embeds that instead)
23
- // EMBEDDED ONLY — enableHyDE not forwarded by HTTP API
24
- await memory.search({ query: 'deployment config', strategy: 'hybrid', enableHyDE: true });
25
-
26
- // With reranking (cross-encoder scores each result for precision)
27
- // EMBEDDED ONLY — enableRerank not forwarded by HTTP API
28
- await memory.search({ query: 'deployment config', strategy: 'hybrid', enableRerank: true });
29
- ```
30
-
31
- ### Retrieval Pipeline (Hybrid Strategy)
32
-
33
- ```
34
- Query → Stage 0: Transform (decompose, HyDE, multi-query)
35
- → Stage 1: Parallel retrieval (BM25/FTS5 + dense vector + graph traverse + community summaries)
36
- → Stage 2: RRF fusion (k=60) + dedup
37
- → Stage 3: Cross-encoder reranking → top-N
38
- ```
39
-
40
- ---
41
-
42
- ## Bi-Temporal Model
43
-
44
- Every entry tracks two timestamps for temporal reasoning:
45
-
46
- - **`eventTime`**: When the fact/event actually occurred
47
- - **`ingestTime`**: When it was stored in memory (auto-set)
48
-
49
- **Sidecar note**: All `StoreInput` fields (including `eventTime`, `id`, `parentId`, `ingestTime`) are forwarded by `MemoryClient.store()`. Temporal search filters (`eventTimeRange`, `asOf`) are forwarded by `MemoryClient.search()`. However, `filters` (source, importanceMin, parentId, contentType), `enableHyDE`, and `enableRerank` are still not forwarded by the search endpoint.
50
-
51
- ```typescript
52
- // Store with explicit event time (works in both embedded and sidecar)
53
- await memory.store({
54
- content: 'User changed address to 123 Main St',
55
- type: 'long-term',
56
- metadata: {},
57
- eventTime: '2026-01-15T00:00:00Z', // when it happened
58
- });
59
-
60
- // Query as-of (available via MemoryClient.queryAsOf() or HTTP endpoint)
61
- const snapshot = await memory.queryAsOf('2026-01-20T00:00:00Z', { type: 'long-term' });
62
-
63
- // Query by event time range (available via MemoryClient.queryByEventTime() or HTTP endpoint)
64
- const events = await memory.queryByEventTime('2026-01-01T00:00:00Z', '2026-02-01T00:00:00Z');
65
-
66
- // Filter by event time range in search (EMBEDDED ONLY via filters param)
67
- await memory.search({
68
- query: 'address',
69
- filters: { eventTimeRange: ['2026-01-01', '2026-02-01'] },
70
- });
71
- ```
72
-
73
- ---
74
-
75
- ## Consolidation Pipeline
76
-
77
- When `consolidate()` runs, it executes a 7-step pipeline:
78
-
79
- 1. **Extract facts** — LLM or regex extraction of factual statements
80
- 2. **Deduplicate** — Content hash + vector similarity (>0.90) → LLM classifies ADD/UPDATE/DELETE/NOOP
81
- 3. **Resolve conflicts** — Detect contradictions, resolve by recency and source trust
82
- 4. **Score importance** — LLM rates 1-10 (or heuristic: recency + access count + entity density)
83
- 5. **Enrich graph** — Extract entities and relationships, merge with existing graph
84
- 6. **Summarize** — Rolling session summaries, memory compaction
85
- 7. **Decay** — Archive entries below importance threshold: `importance * 0.995^hours * (1 + 0.02 * min(accessCount, 20)) * eventAgeFactor`
86
-
87
- All steps have **non-LLM fallbacks** — consolidation works without an LLM, just less intelligently.
88
-
89
- ### Pinned entries (opt out of consolidation)
90
-
91
- Entries stored with `pinned: true` are exempt from both the dedup step and the decay-archive step. `Memory.runDecay()` skips them via the same filter. Use for anchor rows whose ID is referenced by external systems — e.g. a file-catalog entry whose ID is stored as a foreign key in a downstream service's database:
92
-
93
- ```typescript
94
- await memory.store({
95
- content: '[File: "report.pdf" (application/pdf)]',
96
- type: MemoryType.LONG_TERM,
97
- metadata: { source: 'file-ingestion', filename: 'report.pdf' },
98
- pinned: true, // external FK points here — must not be merged or archived
99
- });
100
- ```
101
-
102
- Without `pinned`, short near-duplicate anchor content can trip the 0.95 cosine dedup threshold and get hard-deleted, orphaning the external reference. Pinned entries' exemption is SQL-side (`WHERE pinned = 0` filter on consolidation's query) and backed by a partial index `idx_memory_pinned WHERE pinned = 1`. Callers can query with `QueryFilters.excludePinned: true` to reproduce the same exclusion.
103
-
104
- ---
105
-
106
- ## Community Detection
107
-
108
- When a `graphStore` is configured, the system can detect communities of related entities using the Louvain algorithm:
109
-
110
- ```typescript
111
- import { CommunityDetector } from '@pyx-memory/core';
112
-
113
- const detector = new CommunityDetector(graphStore, llm);
114
- const communities = await detector.detect();
115
- // Each community: { id, nodeIds, summary? }
116
- // Summaries are used by hybrid RAG for corpus-level queries
117
- ```
118
-
119
- Communities are leveraged by the hybrid RAG strategy to answer broad "what are the themes" queries.
120
-
121
- ---
122
-
123
- ## Multi-Tenant Isolation
124
-
125
- pyx-memory supports multi-tenant data isolation via three scoping fields: `tenantId`, `userId`, and `teamId`.
126
-
127
- ### Embedded mode
128
-
129
- ```typescript
130
- // Instance-level scoping — auto-scopes ALL operations
131
- const memory = new Memory({
132
- dataDir: './data',
133
- tenantId: 'tenant-abc',
134
- });
135
-
136
- // Or per-operation scoping
137
- await memory.store({
138
- content: 'Fact for this tenant.',
139
- tenantId: 'tenant-abc',
140
- userId: 'user-123',
141
- teamId: 'team-eng',
142
- });
143
-
144
- // Search is auto-scoped to the instance tenantId
145
- const results = await memory.search({ query: 'facts', limit: 5 });
146
-
147
- // get/delete/clearSession/stats accept TenantScopeOptions
148
- const entry = await memory.get('entry-id', { tenantId: 'tenant-abc' });
149
- ```
150
-
151
- ### Sidecar mode
152
-
153
- ```typescript
154
- // Set tenant headers via MemoryClientOptions
155
- const memory = new MemoryClient('http://localhost:7822', {
156
- apiKey: process.env.MEMORY_API_KEY,
157
- defaultHeaders: { 'X-Tenant-Id': 'tenant-abc', 'X-User-Id': 'user-123' },
158
- });
159
-
160
- // Or pass tenantId per-operation in the request body
161
- await memory.store({
162
- content: 'Tenant-scoped memory.',
163
- tenantId: 'tenant-abc',
164
- userId: 'user-123',
165
- });
166
- ```
167
-
168
- ### Server enforcement
169
-
170
- Set `TENANT_MODE=multi` to require `X-Tenant-Id` on all operations. Requests without it are rejected (HTTP 400). When `TENANT_MODE=single` (default), tenant fields are stored but not enforced — backward compatible.
171
-
172
- Content hash dedup is scoped by `tenantId` to prevent cross-tenant collisions.
173
-
174
- ---
175
-
176
- ## Sensitivity Classification & Encryption
177
-
178
- pyx-memory auto-classifies content sensitivity and optionally encrypts sensitive data at rest.
179
-
180
- ### How it works
181
-
182
- 1. Every `store()` scans content for credentials and PII
183
- 2. Auto-classifies as `public`, `internal` (PII), or `secret` (credentials)
184
- 3. Behavior depends on `SENSITIVITY_POLICY` env var (or embedded config):
185
- - `flag`: classify only, no content modification (default)
186
- - `redact`: replace detected credentials with `[REDACTED]`
187
- - `block`: reject ingestion (HTTP 400)
188
- - `encrypt`: encrypt `secret` entries at rest with AES-256-GCM
189
-
190
- ### Embedded encryption
191
-
192
- ```typescript
193
- const memory = new Memory({
194
- dataDir: './data',
195
- encryptionKey: Buffer.from(process.env.ENCRYPTION_KEY!, 'hex'), // 32 bytes
196
- });
197
-
198
- // Entries classified as 'secret' are automatically encrypted before storage
199
- // Decryption is automatic on get() when the encryption key is available
200
- ```
201
-
202
- ### Search access control
203
-
204
- ```typescript
205
- // Embedded: filter by max sensitivity
206
- const results = await memory.search({
207
- query: 'config',
208
- maxSensitivity: 'internal', // secret entries are redacted in results
209
- });
210
-
211
- // Sidecar: via header
212
- const memory = new MemoryClient('http://localhost:7822', {
213
- apiKey: 'key',
214
- defaultHeaders: { 'X-Caller-Access-Level': 'internal' },
215
- });
216
- ```
217
-
218
- ---
219
-
220
- ## Confidence / Abstention
221
-
222
- Search results can include a confidence score that enables "I don't know" responses when retrieval quality is low.
223
-
224
- ```typescript
225
- const results = await memory.search({
226
- query: 'user preferences',
227
- strategy: 'hybrid',
228
- abstentionThreshold: 0.5, // 0-1, default 0.3
229
- });
230
-
231
- if (results.confidence?.shouldAbstain) {
232
- // Confidence is below threshold — don't use these results
233
- return "I don't have enough information to answer that.";
234
- }
235
- ```
236
-
237
- The confidence score is computed from four signals: top score magnitude, score gap (top vs second), score spread (stdDev), and score separation (top vs mean). It's automatically included when using hybrid, graph, or agentic strategies.
238
-
239
- ---
240
-
241
- ## Automatic Behaviors
242
-
243
- These happen automatically — no configuration needed:
244
-
245
- - **PII detection**: Every `store()` scans content and sets `metadata.piiDetected` + `metadata.piiTypes` if found
246
- - **Sensitivity classification**: Every `store()` auto-classifies content as `public`, `internal`, or `secret`
247
- - **Content hashing**: Every `store()` computes SHA-256 `contentHash`
248
- - **Access tracking**: Every `search()` increments `accessCount` and updates `lastAccessed` on returned entries
249
- - **FTS5 sync**: SQLite triggers keep full-text search index in sync with memory_entries
250
- - **Graph storage**: When `targets` includes `'graph'`, agent-provided `entities` are stored to the graph (best-effort — failures don't block store)
251
- - **Auto-registration on import**: Importing `@pyx-memory/core` registers StubEmbeddingProvider, LanceDBProvider, and NaiveRAGEngine
252
- - **Graph/Agentic RAG registration**: Memory constructor auto-registers GraphRAGEngine and AgenticRAGEngine when you pass `graphStore` or `reasoningProvider`
253
- - **Agent scoping**: If `agentId` is set in MemoryOptions, all operations auto-filter by that agent
254
- - **Tenant scoping**: If `tenantId` is set in MemoryOptions, all operations auto-filter by that tenant
255
-
256
- ---
257
-
258
- ## Initialization Sequence
259
-
260
- ```
261
- 1. (Optional) Create and await graphStore.initialize()
262
- 2. Construct: new Memory({ graphStore?, llm?, ... }) ← embedding is internal (BGE-M3)
263
- 3. Await: memory.initialize() ← creates SQLite DB + LanceDB vector store
264
- 4. Use: store(), search(), etc.
265
- 5. Cleanup: await memory.shutdown()
266
- 6. (Optional) await graphStore.shutdown()
267
- ```
268
-
269
- **Memory.initialize()** creates:
270
- - SQLite database at `{dataDir}/memory/memory.db` (with FTS5 + migrations)
271
- - LanceDB vector store at `{dataDir}/vectors/`
272
- - Both directories are created automatically (mkdirSync recursive)
273
-
274
- **Memory does NOT** call `graphStore.initialize()` — you must do this yourself before or after constructing Memory, but before calling `memory.initialize()`.