@pyxmate/memory 0.20.5 → 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.
- package/LICENSE +1 -1
- package/README.md +63 -34
- package/dist/chunk-7P6ASYW6.mjs +9 -0
- package/dist/cli/pyx-mem.mjs +15705 -0
- package/dist/dashboard.mjs +1 -0
- package/dist/index.mjs +1 -0
- package/dist/react.mjs +1 -0
- package/package.json +7 -4
- package/bin/init.mjs +0 -672
- package/skills/pyx-memory/SKILL.md +0 -128
- package/skills/pyx-memory/examples/disabled-memory.ts +0 -53
- package/skills/pyx-memory/examples/minimal-embedded.ts +0 -37
- package/skills/pyx-memory/examples/minimal-sidecar.ts +0 -14
- package/skills/pyx-memory/patterns/access-control.md +0 -586
- package/skills/pyx-memory/patterns/consumer.md +0 -129
- package/skills/pyx-memory/patterns/embedded.md +0 -249
- package/skills/pyx-memory/patterns/file-uploads.md +0 -78
- package/skills/pyx-memory/reference/advanced.md +0 -274
- package/skills/pyx-memory/reference/http-api.md +0 -526
- package/skills/pyx-memory/reference/parity.md +0 -74
- package/skills/pyx-memory/reference/sdk-guide.md +0 -233
- package/skills/pyx-memory/reference/types.md +0 -344
|
@@ -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()`.
|