vektor-slipstream 1.3.5 → 1.3.7

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 ADDED
@@ -0,0 +1,139 @@
1
+ # VEKTOR v1.3.7 — Changelog
2
+
3
+ Released: 2026-04-05
4
+
5
+ ## New features
6
+
7
+ ### memory.forget(id)
8
+ Hard-deletes a memory and cascades to all graph edges. Throws if memory is pinned — must unpin first.
9
+ ```js
10
+ await memory.forget('mem_abc123');
11
+ // → { deleted: true, edgesRemoved: 4 }
12
+ ```
13
+
14
+ ### memory.forgetWhere(query, opts)
15
+ Semantic bulk-delete. Preview with `dryRun: true` before committing.
16
+ ```js
17
+ await memory.forgetWhere('Project Atlas', { dryRun: true });
18
+ await memory.forgetWhere('Project Atlas', { minScore: 0.9, limit: 5 });
19
+ ```
20
+
21
+ ### memory.pin(id) / memory.unpin(id) / memory.listPinned()
22
+ Mark memories as permanent — REM cycle will never compress or delete them.
23
+ ```js
24
+ const { id } = await memory.remember('User is allergic to peanuts');
25
+ await memory.pin(id);
26
+ await memory.listPinned(); // → all pinned memories
27
+ ```
28
+
29
+ ### memory.inspect()
30
+ Full diagnostic snapshot — no more raw SQLite queries to debug state.
31
+ ```js
32
+ const state = memory.inspect();
33
+ state.counts // → { memories: 247, pinned: 3, edges: 7180 }
34
+ state.rem // → { totalDreams: 11, compressionRatio: '0.06' }
35
+ state.graph.topNodes // → 5 most-connected memories
36
+ ```
37
+
38
+ ### memory.auditLog(opts) / memory.auditStats()
39
+ Full AUDN decision history. Nothing disappears silently.
40
+ ```js
41
+ memory.auditLog({ action: 'DELETE', since: '7d' });
42
+ // → [{ action, memory_id, content, reason, similarity, ran_at }]
43
+ ```
44
+
45
+ ### memory.briefing({ since }) — scoped date filtering
46
+ ```js
47
+ await memory.briefing({ since: '7d' });
48
+ await memory.briefing({ since: '12h', minImportance: 0.7 });
49
+ await memory.briefing({ since: '2026-01-01', raw: true });
50
+ ```
51
+
52
+ ### memory.export() / memory.import()
53
+ Full graph serialisation with checksum integrity.
54
+ ```js
55
+ const bundle = memory.export();
56
+ fs.writeFileSync('backup.vektor.json', JSON.stringify(bundle));
57
+
58
+ memory.import(bundle);
59
+ // → { imported: 244, skipped: 3, edgesAdded: 7100 }
60
+
61
+ memory.import(bundle, { dryRun: true }); // preview
62
+ memory.import(bundle, { mode: 'replace' }); // wipe and restore
63
+ ```
64
+
65
+ ### Namespace support
66
+ Scope all reads/writes to prevent project bleed.
67
+ ```js
68
+ const memory = await createMemory({ agentId: 'my-agent', namespace: 'project-atlas' });
69
+ memory.listNamespaces();
70
+ // → [{ namespace: 'project-atlas', memories: 18 }, ...]
71
+ ```
72
+
73
+ ### TypeScript types
74
+ Full `.d.ts` declarations for all public API methods. No more flying blind in your IDE.
75
+ Add to `tsconfig.json`: `"types": ["vektor-slipstream"]`
76
+
77
+ ### Structured error codes
78
+ Replace string catch blocks with reliable `VektorError.code` switching.
79
+ ```js
80
+ import { VektorError, ERR } from 'vektor-slipstream/errors';
81
+
82
+ try {
83
+ await memory.remember(...)
84
+ } catch (e) {
85
+ if (e instanceof VektorError) {
86
+ switch (e.code) {
87
+ case ERR.LICENCE_INVALID: // handle
88
+ case ERR.EMBED_FAILED: // retry
89
+ }
90
+ }
91
+ }
92
+ ```
93
+
94
+ ## Schema changes
95
+ Run `npx vektor migrate` after updating, or migration runs automatically on startup.
96
+
97
+ New columns:
98
+ - `memories.pinned` INTEGER DEFAULT 0
99
+ - `memories.namespace` TEXT DEFAULT 'default'
100
+ - `memories.updated_at` INTEGER
101
+
102
+ New tables:
103
+ - `audn_log` — AUDN decision audit trail
104
+ - `rem_log` — REM cycle run history (if not already present)
105
+ - `vektor_meta` — schema version tracking
106
+
107
+ ## Bug fixes
108
+ - REM cycle now skips pinned memories (previously could compress critical facts)
109
+ - AUDN deletions now logged before execution (previously silent)
110
+ - briefing() no longer returns stale summary when no memories exist in window
111
+
112
+ ## Breaking changes
113
+ None. All new methods are additive. Existing code works unchanged.
114
+ Migration is backward-compatible — new columns have safe defaults.
115
+
116
+ ## Gemini security review fixes (round 1 — applied)
117
+ - `briefing.js` — pinned memory query now has LIMIT 50 (context bomb prevention)
118
+ - `briefing.js` — `parseSince()` guards against `Date.now()` ms being passed as unix seconds
119
+ - `forget.js` — `forgetWhere()` selective error catching — infra errors re-thrown, only expected skips swallowed
120
+ - `namespace.js` — O(N) JS delete loop replaced with bulk SQL subquery (2 statements vs N*2)
121
+ - `inspect.js` — all `SUM()` aggregations wrapped in `IFNULL(..., 0)` — no more `null` in charts
122
+ - `migrate-137.js` — `PRAGMA table_info()` replaces brittle error-message string matching
123
+
124
+ ## Gemini security review fixes (round 2 — applied)
125
+ - `export-import.js` — Fix 1: `IN (...memIds)` placeholders replaced with SQL subqueries — no more 32,766 param limit crash on large exports
126
+ - `export-import.js` — Fix 2: O(N) loop in `replace` mode replaced with bulk subquery deletes
127
+ - `export-import.js` — Fix 3: checksum now hashes full memories payload — content/importance tampering now detectable
128
+ - `export-import.js` — Fix 4: dangling edge guard — edges only inserted if both source and target exist in destination (`WHERE EXISTS`)
129
+ - `export-import.js` — `edgesSkipped` added to import return value with console.warn for partial imports
130
+
131
+ ## Gemini security review fixes (round 3 — applied)
132
+ - `forget.js` — pinned edge orphan guard: blocks deleting a memory that shares
133
+ a graph edge with a pinned memory — prevents silent MAGMA traversal corruption
134
+ - `namespace.js` — SQLITE_BUSY retry loop: 3 attempts × 100ms using
135
+ `Atomics.wait` (correct sync sleep for better-sqlite3) before propagating error
136
+ - `export-import.js` — confirmed: empty bundle guard (line 103) already sits
137
+ before checksum computation (line 107) — correct order, no change needed
138
+
139
+ ## Total fixes across 3 Gemini review rounds: 13
package/README.md CHANGED
@@ -2,13 +2,18 @@
2
2
 
3
3
  Hardware-accelerated persistent memory for AI agents. Local-first. No cloud. One-time payment.
4
4
 
5
+ [![npm](https://img.shields.io/npm/v/vektor-slipstream)](https://www.npmjs.com/package/vektor-slipstream)
6
+ [![license](https://img.shields.io/badge/license-Commercial-blue)](https://vektormemory.com/product#pricing)
7
+
5
8
  ## Install
9
+
6
10
  ```bash
7
11
  npm install vektor-slipstream
8
12
  npx vektor setup
9
13
  ```
10
14
 
11
15
  ## Quick Start
16
+
12
17
  ```js
13
18
  const { createMemory } = require('vektor-slipstream');
14
19
 
@@ -17,36 +22,216 @@ const memory = await createMemory({
17
22
  licenceKey: process.env.VEKTOR_LICENCE_KEY,
18
23
  });
19
24
 
25
+ // Store a memory
20
26
  await memory.remember('User prefers TypeScript over JavaScript');
21
- const results = await memory.recall('coding preferences');
27
+
28
+ // Recall by semantic similarity — avg 8ms, fully local
29
+ const results = await memory.recall('coding preferences', 5);
22
30
  // → [{ content, score, id }]
31
+
32
+ // Traverse the graph
33
+ const graph = await memory.graph('TypeScript', { hops: 2 });
34
+
35
+ // What changed in 7 days?
36
+ const delta = await memory.delta('project decisions', 7);
37
+
38
+ // Morning briefing
39
+ const brief = await memory.briefing();
23
40
  ```
24
41
 
25
42
  ## CLI
43
+
26
44
  ```bash
27
- npx vektor setup # First-run wizard
28
- npx vektor activate # Activate licence key
29
- npx vektor test # Test memory engine
30
- npx vektor status # System health check
31
- npx vektor mcp # Start Claude MCP server
32
- npx vektor rem # Run REM dream cycle
33
- npx vektor help # All commands
45
+ npx vektor setup # First-run wizard — licence, hardware, integrations
46
+ npx vektor activate # Activate licence key on this machine
47
+ npx vektor test # Test memory engine with progress bar
48
+ npx vektor status # System health check
49
+ npx vektor mcp # Start Claude Desktop MCP server
50
+ npx vektor rem # Run REM dream cycle
51
+ npx vektor help # All commands
52
+ ```
53
+
54
+ ## CLOAK — Developer Context Layer for Claude Code
55
+
56
+ CLOAK is a 4-tool MCP layer that gives Claude Code persistent memory of your project.
57
+ Every session starts with full context. Every error is remembered. Every wasted token is tracked.
58
+
59
+ ### What it does
60
+
61
+ | Tool | Does exactly one thing |
62
+ |---|---|
63
+ | `cloak_cortex` | Scans project files, builds a token-aware anatomy index in Vektor's entity graph |
64
+ | `cloak_axon` | Session boundary handler — loads last context on start, saves MemCell on stop |
65
+ | `cloak_cerebellum` | Pre-write enforcer — checks writes against known error patterns, auto-resolves fixes |
66
+ | `cloak_token` | Token ledger — detects repeated reads and waste, one summary write per session |
67
+
68
+ ### Import
69
+
70
+ ```js
71
+ const { runCortex } = require('vektor-slipstream/cloak/cortex');
72
+ const { onSessionStart,
73
+ onSessionStop } = require('vektor-slipstream/cloak/axon');
74
+ const { checkWrite,
75
+ recordError } = require('vektor-slipstream/cloak/cerebellum');
76
+ const { getSessionSummary} = require('vektor-slipstream/cloak/token');
77
+
78
+ // Clean terminal output (replaces emoji with box-drawing chars)
79
+ const { createMemory } = require('vektor-slipstream/boot');
80
+ ```
81
+
82
+ ### Claude Code setup
83
+
84
+ Add to `.claude/settings.json` in your project:
85
+
86
+ ```json
87
+ {
88
+ "mcpServers": {
89
+ "cloak": {
90
+ "command": "node",
91
+ "args": ["/path/to/node_modules/vektor-slipstream/index.js"],
92
+ "env": {
93
+ "VEKTOR_LICENCE_KEY": "your-licence-key",
94
+ "CLOAK_PROJECT_PATH": "/path/to/your/project"
95
+ }
96
+ }
97
+ },
98
+ "hooks": {
99
+ "SessionStart": [{ "type": "command", "command": "node /path/to/node_modules/vektor-slipstream/index.js --hook SessionStart" }],
100
+ "Stop": [{ "type": "command", "command": "node /path/to/node_modules/vektor-slipstream/index.js --hook Stop" }],
101
+ "PreToolUse": [{ "type": "command", "command": "node /path/to/node_modules/vektor-slipstream/index.js --hook PreToolUse" }],
102
+ "PostToolUse": [{ "type": "command", "command": "node /path/to/node_modules/vektor-slipstream/index.js --hook PostToolUse" }]
103
+ }
104
+ }
105
+ ```
106
+
107
+ ### Usage
108
+
109
+ ```js
110
+ // Scan project files into Vektor entity graph (call once on init)
111
+ await cloak_cortex({})
112
+
113
+ // Start session — loads last context automatically
114
+ await cloak_axon({ action: 'start' })
115
+
116
+ // Check before a risky write
117
+ await cloak_cerebellum({
118
+ action: 'check',
119
+ content: '...code...',
120
+ filePath: 'src/auth.ts'
121
+ })
122
+
123
+ // Record a bug you just hit
124
+ await cloak_cerebellum({
125
+ action: 'record_error',
126
+ description: 'Null ref on user.id when DB returns empty',
127
+ filePath: 'src/auth.ts',
128
+ errorType: 'null_reference',
129
+ fix: 'Add guard: if (user && user.id)'
130
+ })
131
+
132
+ // Save session with intent — the "why" not just the "what"
133
+ await cloak_axon({
134
+ action: 'stop',
135
+ narrative: 'Fixed null reference in auth module'
136
+ })
137
+
138
+ // Check token waste
139
+ await cloak_token({ action: 'summary' })
140
+ ```
141
+
142
+ ### Architecture
143
+
34
144
  ```
145
+ Claude Code
146
+ ↓ hooks + MCP tool calls
147
+ CLOAK (4 tools, ~800 lines Node.js)
148
+ ↓ memory.remember() / memory.recall()
149
+ Vektor / MAGMA
150
+ ↓ 4-layer graph (semantic, temporal, causal, entity)
151
+ SQLite-vec (local, no cloud)
152
+ ```
153
+
154
+ ### DB growth
155
+
156
+ CLOAK writes 3-5 nodes per session — not per operation.
157
+ At 1 year of daily use: ~1,000 nodes. Sub-millisecond recall throughout.
158
+
159
+ > ⚠️ **Concurrency warning**: Use stdio MCP (default). The HTTP mode
160
+ > (`CLOAK_HTTP=1`) does not support concurrent multi-agent sessions.
161
+
162
+ ---
35
163
 
36
164
  ## What's Included
37
165
 
38
- - **MAGMA graph** — 4-layer associative memory (semantic, causal, temporal, entity)
39
- - **AUDN curation** — zero contradictions, zero duplicates
40
- - **REM dream cycle**50:1 memory compression
41
- - **Claude MCP server**persistent memory for Claude Desktop
42
- - **Cloak** stealth browser, AES-256 vault, layout sensor
43
- - **Mistral bridge**vektor_memoire tool for Le Chat
44
- - **LangChain · OpenAI · Gemini · Groq · Ollama** all supported
45
- - **Local ONNX embeddings** — $0 embedding cost, no API key
46
- - **Single SQLite file** — portable, yours forever
166
+ ### Memory Core (MAGMA)
167
+
168
+ - 4-layer associative graphsemantic, causal, temporal, entity
169
+ - AUDN curation loopzero contradictions, zero duplicates
170
+ - REM dream cycle up to 50:1 compression
171
+ - Sub-20ms recallHNSW index, local SQLite
172
+ - Local ONNX embeddings $0 embedding cost, no API key required
173
+
174
+ ### Integrations
175
+
176
+ - **Claude MCP** — `vektor_recall`, `vektor_store`, `vektor_graph`, `vektor_delta`
177
+ - **Claude Code** — CLOAK 4-tool developer context layer (see above)
178
+ - **LangChain** — v1 + v2 adapter included
179
+ - **OpenAI Agents SDK** — drop-in integration
180
+ - **Mistral** — `vektor_memoire` HTTP tool, localhost bridge
181
+ - **Gemini · Groq · Ollama** — provider agnostic
182
+
183
+ ### Cloak (Sovereign Identity)
184
+
185
+ - `cloak_fetch` — stealth headless browser fetch
186
+ - `cloak_render` — computed CSS · post-JS DOM sensor
187
+ - `cloak_passport` — AES-256-GCM credential vault, machine-bound
188
+ - `cloak_diff` — semantic diff since last fetch
189
+ - `tokens_saved` — ROI audit per session
190
+
191
+ ```js
192
+ const { cloak_passport, cloak_fetch, tokens_saved } = require('vektor-slipstream/cloak');
193
+
194
+ cloak_passport('GITHUB_TOKEN', 'ghp_xxxx');
195
+ const token = cloak_passport('GITHUB_TOKEN');
196
+
197
+ const { text, tokensSaved } = await cloak_fetch('https://example.com');
198
+
199
+ const roi = tokens_saved({ raw_tokens: 10000, actual_tokens: 3000 });
200
+ // → { reduction_pct: 70, cost_saved_usd: 0.0175, roi_multiple: 2.3 }
201
+ ```
202
+
203
+ ## Performance
204
+
205
+ | Metric | Value |
206
+ |--------|-------|
207
+ | Recall latency | ~8ms avg (local SQLite) |
208
+ | Embedding cost | $0 — fully local ONNX |
209
+ | Embedding latency | ~10ms GPU / ~25ms CPU |
210
+ | First run | ~2 min (downloads ~25MB model once) |
211
+ | Subsequent boots | <100ms |
212
+
213
+ ## Hardware Auto-Detection
214
+
215
+ Zero config. VEKTOR detects and uses the best available accelerator:
216
+
217
+ - **NVIDIA CUDA** — GPU acceleration
218
+ - **Apple Silicon** — CoreML
219
+ - **CPU** — optimised fallback, works everywhere
47
220
 
48
221
  ## Licence
49
222
 
50
- Commercial licence. One-time payment. 3-machine activation via Polar.
51
- Purchase at: https://vektormemory.com/product#pricing
52
- Support: hello@vektormemory.com
223
+ Commercial licence. One-time payment of $159. Activates on up to 3 machines.
224
+ Manage at [polar.sh](https://polar.sh).
225
+
226
+ Purchase: [vektormemory.com/product#pricing](https://vektormemory.com/product#pricing)
227
+ Docs: [vektormemory.com/docs](https://vektormemory.com/docs)
228
+ Support: hello@vektormemory.com
229
+
230
+ ## Research
231
+
232
+ Built on peer-reviewed research:
233
+
234
+ - [MAGMA (arxiv:2601.03236)](https://arxiv.org/abs/2601.03236) — Multi-Graph Agentic Memory Architecture
235
+ - [EverMemOS (arxiv:2601.02163)](https://arxiv.org/abs/2601.02163) — Self-Organizing Memory OS
236
+ - [HippoRAG (arxiv:2405.14831)](https://arxiv.org/abs/2405.14831) — Neurobiologically Inspired Long-Term Memory (NeurIPS 2024)
237
+ - [Mem0 (arxiv:2504.19413)](https://arxiv.org/abs/2504.19413) — Production-Ready Agent Memory
package/audn-log.js ADDED
@@ -0,0 +1,143 @@
1
+ /**
2
+ * VEKTOR AUDN audit log — v1.3.7
3
+ * Records every ADD/UPDATE/DELETE/NO_OP decision AUDN makes
4
+ * before acting on it. Full transparency — nothing disappears silently.
5
+ *
6
+ * Schema additions required:
7
+ * CREATE TABLE IF NOT EXISTS audn_log (
8
+ * id INTEGER PRIMARY KEY AUTOINCREMENT,
9
+ * agent_id TEXT NOT NULL,
10
+ * namespace TEXT NOT NULL,
11
+ * action TEXT NOT NULL, -- ADD | UPDATE | DELETE | NO_OP
12
+ * memory_id TEXT, -- affected memory id (null for ADD before insert)
13
+ * content TEXT, -- snapshot of content at time of action
14
+ * reason TEXT, -- why AUDN made this decision
15
+ * similarity REAL, -- similarity score that triggered the decision
16
+ * ran_at INTEGER DEFAULT (unixepoch())
17
+ * );
18
+ * CREATE INDEX IF NOT EXISTS idx_audn_log_agent ON audn_log(agent_id, namespace, ran_at);
19
+ */
20
+
21
+ /**
22
+ * logAudn — call this inside AUDN before executing each decision
23
+ *
24
+ * @param {object} db
25
+ * @param {string} agentId
26
+ * @param {string} namespace
27
+ * @param {object} entry
28
+ * @param {string} entry.action - ADD | UPDATE | DELETE | NO_OP
29
+ * @param {string} entry.memoryId - memory id (if known)
30
+ * @param {string} entry.content - content snapshot
31
+ * @param {string} entry.reason - human-readable reason
32
+ * @param {number} entry.similarity - cosine score that triggered this
33
+ */
34
+ export function logAudn(db, agentId, namespace, entry) {
35
+ db.prepare(`
36
+ INSERT INTO audn_log (agent_id, namespace, action, memory_id, content, reason, similarity)
37
+ VALUES (?, ?, ?, ?, ?, ?, ?)
38
+ `).run(
39
+ agentId,
40
+ namespace,
41
+ entry.action,
42
+ entry.memoryId || null,
43
+ entry.content ? entry.content.slice(0, 500) : null,
44
+ entry.reason || null,
45
+ entry.similarity || null
46
+ );
47
+ }
48
+
49
+ /**
50
+ * auditLog(opts) — retrieve AUDN decision history
51
+ *
52
+ * @param {object} db
53
+ * @param {string} agentId
54
+ * @param {string} namespace
55
+ * @param {object} opts
56
+ * @param {number} opts.limit - max rows (default 50)
57
+ * @param {string} opts.action - filter to specific action (ADD|UPDATE|DELETE|NO_OP)
58
+ * @param {string} opts.since - time filter — '7d', '24h', ISO date
59
+ * @returns {object[]}
60
+ */
61
+ export function auditLog(db, agentId, namespace, opts = {}) {
62
+ const { limit = 50, action = null, since = null } = opts;
63
+
64
+ let sinceTs = null;
65
+ if (since) {
66
+ const rel = String(since).match(/^(\d+)(d|h|m)$/);
67
+ if (rel) {
68
+ const n = parseInt(rel[1]);
69
+ const unit = rel[2];
70
+ const seconds = unit === 'd' ? n * 86400 : unit === 'h' ? n * 3600 : n * 60;
71
+ sinceTs = Math.floor(Date.now() / 1000) - seconds;
72
+ } else {
73
+ sinceTs = Math.floor(Date.parse(since) / 1000);
74
+ }
75
+ }
76
+
77
+ const actionClause = action ? `AND action = '${action.toUpperCase()}'` : '';
78
+ const sinceClause = sinceTs ? `AND ran_at >= ${sinceTs}` : '';
79
+
80
+ return db.prepare(`
81
+ SELECT id, action, memory_id, content, reason, similarity, ran_at
82
+ FROM audn_log
83
+ WHERE agent_id = ? AND namespace = ?
84
+ ${actionClause}
85
+ ${sinceClause}
86
+ ORDER BY ran_at DESC
87
+ LIMIT ?
88
+ `).all(agentId, namespace, limit);
89
+ }
90
+
91
+ /**
92
+ * auditStats() — summary counts by action type
93
+ */
94
+ export function auditStats(db, agentId, namespace) {
95
+ return db.prepare(`
96
+ SELECT
97
+ action,
98
+ COUNT(*) as count,
99
+ MAX(ran_at) as last_at
100
+ FROM audn_log
101
+ WHERE agent_id = ? AND namespace = ?
102
+ GROUP BY action
103
+ ORDER BY count DESC
104
+ `).all(agentId, namespace);
105
+ }
106
+
107
+ /**
108
+ * pruneAuditLog(days) — keep log from growing forever
109
+ * Call from REM cycle or a scheduled job
110
+ */
111
+ export function pruneAuditLog(db, agentId, namespace, keepDays = 30) {
112
+ const cutoff = Math.floor(Date.now() / 1000) - keepDays * 86400;
113
+ const result = db.prepare(
114
+ `DELETE FROM audn_log WHERE agent_id = ? AND namespace = ? AND ran_at < ?`
115
+ ).run(agentId, namespace, cutoff);
116
+ return { pruned: result.changes };
117
+ }
118
+
119
+ /**
120
+ * Integration into Memory class / AUDN loop:
121
+ *
122
+ * // Inside AUDN decision logic:
123
+ * logAudn(this.db, this.agentId, this.namespace, {
124
+ * action: 'DELETE',
125
+ * memoryId: existing.id,
126
+ * content: existing.content,
127
+ * reason: 'Contradicts newer memory with score 0.94',
128
+ * similarity: 0.94
129
+ * });
130
+ * // then execute the delete
131
+ *
132
+ * // Expose on Memory class:
133
+ * auditLog(opts = {}) {
134
+ * return auditLog(this.db, this.agentId, this.namespace, opts);
135
+ * }
136
+ *
137
+ * Usage:
138
+ * memory.auditLog({ action: 'DELETE', since: '7d' });
139
+ * // → [{ id, action:'DELETE', content:'...', reason:'...', ran_at }]
140
+ *
141
+ * memory.auditLog({ limit: 100 });
142
+ * // → last 100 AUDN decisions
143
+ */
package/briefing.js ADDED
@@ -0,0 +1,150 @@
1
+ /**
2
+ * VEKTOR briefing() date scoping — v1.3.7
3
+ * Adds `since` param to briefing() — filter to last N days/hours
4
+ * or an explicit ISO timestamp.
5
+ *
6
+ * Drop-in replacement for existing briefing() implementation.
7
+ */
8
+
9
+ import { VektorError, ERR } from './errors.js';
10
+
11
+ /**
12
+ * Parse a `since` value into a Unix timestamp (seconds).
13
+ * Accepts:
14
+ * '1d' → last 1 day
15
+ * '7d' → last 7 days
16
+ * '12h' → last 12 hours
17
+ * '30m' → last 30 minutes
18
+ * '2026-01-01T00:00:00Z' → explicit ISO date
19
+ * 1735689600 → raw unix timestamp
20
+ */
21
+ export function parseSince(since) {
22
+ if (!since) return null;
23
+
24
+ // Raw unix timestamp — guard against Date.now() (ms) being passed accidentally
25
+ // If number > 9999999999 it's milliseconds (year ~2001 in seconds) — convert it
26
+ if (typeof since === 'number') {
27
+ return since > 9_999_999_999 ? Math.floor(since / 1000) : since;
28
+ }
29
+
30
+ // Relative shorthand: 7d, 12h, 30m
31
+ const rel = String(since).match(/^(\d+)(d|h|m)$/);
32
+ if (rel) {
33
+ const n = parseInt(rel[1]);
34
+ const unit = rel[2];
35
+ const seconds = unit === 'd' ? n * 86400 : unit === 'h' ? n * 3600 : n * 60;
36
+ return Math.floor(Date.now() / 1000) - seconds;
37
+ }
38
+
39
+ // ISO date string
40
+ const ts = Date.parse(since);
41
+ if (!isNaN(ts)) return Math.floor(ts / 1000);
42
+
43
+ throw new VektorError(
44
+ ERR.MEMORY_READ_FAILED,
45
+ `Invalid since value: "${since}". Use "7d", "12h", "30m", ISO date, or unix timestamp.`
46
+ );
47
+ }
48
+
49
+ /**
50
+ * briefing({ since, limit, minImportance })
51
+ * Returns a structured summary of recent memories for agent context injection.
52
+ *
53
+ * @param {object} db
54
+ * @param {string} agentId
55
+ * @param {string} namespace
56
+ * @param {Function} summariseFn - LLM summarise function (existing REM summariser)
57
+ * @param {object} opts
58
+ * @param {string|number} opts.since - time filter (default: '1d')
59
+ * @param {number} opts.limit - max memories (default: 20)
60
+ * @param {number} opts.minImportance - filter by importance score (default: 0)
61
+ * @param {boolean} opts.raw - return raw memories, skip LLM summary
62
+ * @returns {object} { period, memoryCount, pinned, summary, memories }
63
+ */
64
+ export async function briefing(db, agentId, namespace, summariseFn, opts = {}) {
65
+ const {
66
+ since = '1d',
67
+ limit = 20,
68
+ minImportance = 0,
69
+ raw = false
70
+ } = opts;
71
+
72
+ const sinceTs = parseSince(since);
73
+ const sinceClause = sinceTs ? `AND created_at >= ${sinceTs}` : '';
74
+ const importanceClause = minImportance > 0 ? `AND importance >= ${minImportance}` : '';
75
+
76
+ // Fetch scoped memories
77
+ const memories = db.prepare(`
78
+ SELECT id, content, summary, importance, pinned, created_at
79
+ FROM memories
80
+ WHERE agent_id = ? AND namespace = ?
81
+ ${sinceClause}
82
+ ${importanceClause}
83
+ ORDER BY importance DESC, created_at DESC
84
+ LIMIT ?
85
+ `).all(agentId, namespace, limit);
86
+
87
+ // Always fetch pinned regardless of time window — hard cap 50 to prevent context bomb
88
+ // (an agent running for months could accumulate hundreds of pinned memories)
89
+ const pinnedLimit = 50;
90
+ const pinned = db.prepare(`
91
+ SELECT id, content, summary, importance, created_at
92
+ FROM memories
93
+ WHERE agent_id = ? AND namespace = ? AND pinned = 1
94
+ ORDER BY importance DESC
95
+ LIMIT ${pinnedLimit}
96
+ `).all(agentId, namespace);
97
+
98
+ if (raw) {
99
+ return {
100
+ period: since,
101
+ memoryCount: memories.length,
102
+ pinned,
103
+ memories
104
+ };
105
+ }
106
+
107
+ // Build prompt context for LLM summary
108
+ const context = [
109
+ pinned.length > 0
110
+ ? `PINNED (permanent):\n${pinned.map(m => `- ${m.summary || m.content}`).join('\n')}`
111
+ : null,
112
+ memories.length > 0
113
+ ? `RECENT (${since}):\n${memories.map(m => `- [${m.importance?.toFixed(2)}] ${m.summary || m.content}`).join('\n')}`
114
+ : 'No recent memories in this period.'
115
+ ].filter(Boolean).join('\n\n');
116
+
117
+ const summary = await summariseFn(
118
+ `Produce a concise agent briefing from these memories. Lead with pinned facts, then recent activity.\n\n${context}`
119
+ );
120
+
121
+ return {
122
+ period: since,
123
+ memoryCount: memories.length,
124
+ pinnedCount: pinned.length,
125
+ pinned,
126
+ memories,
127
+ summary
128
+ };
129
+ }
130
+
131
+ /**
132
+ * Integration into Memory class:
133
+ *
134
+ * async briefing(opts = {}) {
135
+ * return briefing(this.db, this.agentId, this.namespace, this._summarise.bind(this), opts);
136
+ * }
137
+ *
138
+ * Usage:
139
+ * await memory.briefing();
140
+ * // → last 24h summary (default)
141
+ *
142
+ * await memory.briefing({ since: '7d' });
143
+ * // → last 7 days
144
+ *
145
+ * await memory.briefing({ since: '2026-01-01', minImportance: 0.7 });
146
+ * // → since Jan 1st, high importance only
147
+ *
148
+ * await memory.briefing({ raw: true, since: '1h' });
149
+ * // → raw memory objects, no LLM call
150
+ */