@postnesia/db 0.1.3 → 0.1.5

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/README.md ADDED
@@ -0,0 +1,63 @@
1
+ # @postnesia/db
2
+
3
+ SQLite database layer for the Postnesia memory system. Provides schema migrations, Gemini embeddings, vector search, access tracking, and importance dynamics.
4
+
5
+ ## Exports
6
+
7
+ ```ts
8
+ import { getDb, queries, createMemory } from '@postnesia/db';
9
+ import { embed } from '@postnesia/db/embeddings';
10
+ import { logAccess } from '@postnesia/db/access';
11
+ import { runConsolidation } from '@postnesia/db/importance';
12
+ ```
13
+
14
+ ## Environment Variables
15
+
16
+ | Variable | Required | Default | Description |
17
+ |---|---|---|---|
18
+ | `DATABASE_URL` | Yes | — | Absolute `file://` URL to the SQLite database |
19
+ | `GEMINI_API_KEY` | Yes | — | Google Gemini API key for embeddings |
20
+ | `EMBEDDING_MODEL` | No | `gemini-embedding-001` | Embedding model name |
21
+ | `EMBEDDING_DIMENSIONS` | No | `768` | Embedding vector dimensions |
22
+
23
+ `DATABASE_URL` must be an absolute path, e.g. `file:///home/user/workspace/memory.db`.
24
+
25
+ ## Setup
26
+
27
+ ### First-time (copies schema into your project and migrates)
28
+
29
+ ```bash
30
+ npx postnesia-migrate-init
31
+ ```
32
+
33
+ ### Apply pending migrations
34
+
35
+ ```bash
36
+ npx postnesia-migrate-dev
37
+ ```
38
+
39
+ ### Seed core memories
40
+
41
+ Seeds the operational guide into the database as a core memory:
42
+
43
+ ```bash
44
+ npx postnesia-seed
45
+ ```
46
+
47
+ ## Bins
48
+
49
+ | Command | Description |
50
+ |---|---|
51
+ | `postnesia-migrate-init` | Copy `schema.prisma` to your project and run initial migration |
52
+ | `postnesia-migrate-dev` | Run `prisma migrate dev` against your database |
53
+ | `postnesia-seed` | Insert core bootstrap memories |
54
+
55
+ ## Schema Overview
56
+
57
+ - `memory` — main memory records with `content`, `content_l1`, `type`, `importance`, `core`, timestamps
58
+ - `tag` / `memory_tag` — many-to-many tag associations
59
+ - `relationship` — directional links between memories
60
+ - `vec_memories` — sqlite-vec virtual table for vector similarity search
61
+ - `access_log` — read access history used by the importance system
62
+ - `journal` — daily journal entries
63
+ - `task` — task records with status and optional session grouping
package/core/BOOTSTRAP.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Memory System — Operational Guide
2
2
 
3
- You have a persistent memory system at `openmind/`. This is not optional — use it actively. Do not rely on defaults or conversation history alone.
3
+ You have a persistent memory system at in a Sqlite memory.db. This is not optional — use it actively. Do not rely on defaults or conversation history alone.
4
4
 
5
5
  ## Architecture
6
6
 
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Access Tracking System
3
+ * Logs when memories are retrieved and updates last_accessed on the memory row.
4
+ * Feeds into L1 decay calculations.
5
+ */
6
+ export interface AccessLogEntry {
7
+ memory_id: number;
8
+ context?: string;
9
+ accessed_at: string;
10
+ }
11
+ /**
12
+ * Record that a memory was accessed (updates both access_log and memory.last_accessed)
13
+ */
14
+ export declare function logAccess(memory_id: number, context?: string): void;
15
+ export declare function getAccessCount(memory_id: number, daysBack?: number): number;
16
+ export declare function getRecentlyAccessed(limit?: number): number[];
17
+ export declare function getAccessHistory(memory_id: number): AccessLogEntry[];
18
+ /**
19
+ * Calculate relevance boost based on access patterns
20
+ * Recent access + frequency = higher relevance
21
+ */
22
+ export declare function calculateAccessBoost(memory_id: number): number;
package/dist/access.js ADDED
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Access Tracking System
3
+ * Logs when memories are retrieved and updates last_accessed on the memory row.
4
+ * Feeds into L1 decay calculations.
5
+ */
6
+ import { getDb, recordAccess } from './index.js';
7
+ /**
8
+ * Record that a memory was accessed (updates both access_log and memory.last_accessed)
9
+ */
10
+ export function logAccess(memory_id, context) {
11
+ try {
12
+ const db = getDb(false);
13
+ recordAccess(db, memory_id, context);
14
+ }
15
+ catch (error) {
16
+ console.error(`[access-tracker] Warning: Could not log access for #${memory_id}:`, error?.message);
17
+ }
18
+ }
19
+ export function getAccessCount(memory_id, daysBack = 30) {
20
+ const db = getDb(true);
21
+ const result = db.prepare(`
22
+ SELECT COUNT(*) as count
23
+ FROM access_log
24
+ WHERE memory_id = ?
25
+ AND accessed_at > datetime('now', '-${daysBack} days')
26
+ `).get(memory_id);
27
+ return result.count;
28
+ }
29
+ export function getRecentlyAccessed(limit = 20) {
30
+ const db = getDb(true);
31
+ const results = db.prepare(`
32
+ SELECT memory_id, COUNT(*) as access_count
33
+ FROM access_log
34
+ WHERE accessed_at > datetime('now', '-7 days')
35
+ GROUP BY memory_id
36
+ ORDER BY access_count DESC, MAX(accessed_at) DESC
37
+ LIMIT ?
38
+ `).all(limit);
39
+ return results.map(r => r.memory_id);
40
+ }
41
+ export function getAccessHistory(memory_id) {
42
+ const db = getDb(true);
43
+ return db.prepare(`
44
+ SELECT memory_id, accessed_at, context
45
+ FROM access_log
46
+ WHERE memory_id = ?
47
+ ORDER BY accessed_at DESC
48
+ LIMIT 50
49
+ `).all(memory_id);
50
+ }
51
+ /**
52
+ * Calculate relevance boost based on access patterns
53
+ * Recent access + frequency = higher relevance
54
+ */
55
+ export function calculateAccessBoost(memory_id) {
56
+ const last7Days = getAccessCount(memory_id, 7);
57
+ const last30Days = getAccessCount(memory_id, 30);
58
+ const recentWeight = last7Days * 2;
59
+ const olderWeight = (last30Days - last7Days);
60
+ const totalScore = recentWeight + olderWeight;
61
+ // Normalize to 0-2 range
62
+ return Math.min(2, totalScore / 5);
63
+ }
@@ -4,7 +4,7 @@
4
4
  * Used by sqlite-vec for semantic similarity search (L2 retrieval)
5
5
  */
6
6
  import 'dotenv/config';
7
- export declare const EMBEDDING_DIMENSIONS = 768;
7
+ export declare const EMBEDDING_DIMENSIONS: number;
8
8
  /**
9
9
  * Generate an embedding vector for a text string.
10
10
  * Returns a Float32Array suitable for sqlite-vec storage.
@@ -5,8 +5,8 @@
5
5
  */
6
6
  import 'dotenv/config';
7
7
  import { GoogleGenAI } from '@google/genai';
8
- export const EMBEDDING_DIMENSIONS = 768;
9
- const EMBEDDING_MODEL = 'gemini-embedding-001';
8
+ export const EMBEDDING_DIMENSIONS = parseInt(process.env.EMBEDDING_DIMENSIONS ?? '768', 10);
9
+ const EMBEDDING_MODEL = process.env.EMBEDDING_MODEL ?? 'gemini-embedding-001';
10
10
  function getClient() {
11
11
  const key = process.env.GEMINI_API_KEY;
12
12
  if (!key)
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Importance Dynamics
3
+ * Adjust memory importance based on age, access patterns, and relationships
4
+ */
5
+ export interface ImportanceFactors {
6
+ baseImportance: number;
7
+ ageDecay: number;
8
+ accessBoost: number;
9
+ relationshipBoost: number;
10
+ finalScore: number;
11
+ }
12
+ /**
13
+ * Calculate age decay factor
14
+ * Recent memories maintain importance, old ones decay unless accessed
15
+ */
16
+ export declare function calculateAgeDecay(timestamp: string): number;
17
+ /**
18
+ * Calculate dynamic importance score
19
+ * Base importance + access boost - age decay
20
+ */
21
+ export declare function calculateDynamicImportance(memory_id: number): ImportanceFactors;
22
+ /**
23
+ * Get memories eligible for L1 based on dynamic scoring.
24
+ */
25
+ export declare function getL1Candidates(limit?: number): Array<{
26
+ id: number;
27
+ type: string;
28
+ content_l1: string;
29
+ timestamp: string;
30
+ last_accessed: string;
31
+ baseImportance: number;
32
+ dynamicScore: number;
33
+ }>;
34
+ /**
35
+ * Consolidation: Review and adjust importances based on access patterns
36
+ */
37
+ export declare function runConsolidation(): {
38
+ reviewed: number;
39
+ boosted: number;
40
+ decayed: number;
41
+ unchanged: number;
42
+ };
@@ -0,0 +1,141 @@
1
+ /**
2
+ * Importance Dynamics
3
+ * Adjust memory importance based on age, access patterns, and relationships
4
+ */
5
+ import { getDb, queries } from './index.js';
6
+ import { calculateAccessBoost } from './access.js';
7
+ /**
8
+ * Calculate relationship boost based on graph connectivity
9
+ * Memories that are hubs in the relationship graph are more valuable
10
+ */
11
+ function calculateRelationshipBoost(memory_id) {
12
+ const db = getDb(true);
13
+ // Count total connections
14
+ const connections = db.prepare(`
15
+ SELECT COUNT(*) as count
16
+ FROM relationship
17
+ WHERE from_id = ? OR to_id = ?
18
+ `).get(memory_id, memory_id);
19
+ // Count connections to high-importance memories (4+)
20
+ const importantConnections = db.prepare(`
21
+ SELECT COUNT(*) as count
22
+ FROM relationship r
23
+ JOIN memory m ON (
24
+ CASE
25
+ WHEN r.from_id = ? THEN m.id = r.to_id
26
+ ELSE m.id = r.from_id
27
+ END
28
+ )
29
+ WHERE (r.from_id = ? OR r.to_id = ?)
30
+ AND m.importance >= 4
31
+ `).get(memory_id, memory_id, memory_id);
32
+ // Boost calculation:
33
+ // - Base: 0.1 per connection (up to +1.0 for 10 connections)
34
+ // - Bonus: +0.2 per important connection
35
+ const baseBoost = Math.min(1.0, connections.count * 0.1);
36
+ const importantBonus = importantConnections.count * 0.2;
37
+ return Math.min(2.0, baseBoost + importantBonus);
38
+ }
39
+ /**
40
+ * Calculate age decay factor
41
+ * Recent memories maintain importance, old ones decay unless accessed
42
+ */
43
+ export function calculateAgeDecay(timestamp) {
44
+ const now = Date.now();
45
+ const memoryDate = new Date(timestamp).getTime();
46
+ const ageInDays = (now - memoryDate) / (1000 * 60 * 60 * 24);
47
+ // No decay for first 7 days
48
+ if (ageInDays <= 7)
49
+ return 0;
50
+ // Gradual decay: -0.1 per week after first week
51
+ const weeksOld = Math.floor((ageInDays - 7) / 7);
52
+ const decay = Math.min(2, weeksOld * 0.1); // Max -2 importance
53
+ return -decay;
54
+ }
55
+ /**
56
+ * Calculate dynamic importance score
57
+ * Base importance + access boost - age decay
58
+ */
59
+ export function calculateDynamicImportance(memory_id) {
60
+ const db = getDb(true);
61
+ const memory = db.prepare(`
62
+ SELECT importance, timestamp
63
+ FROM memory
64
+ WHERE id = ?
65
+ `).get(memory_id);
66
+ if (!memory) {
67
+ return {
68
+ baseImportance: 0,
69
+ ageDecay: 0,
70
+ accessBoost: 0,
71
+ relationshipBoost: 0,
72
+ finalScore: 0,
73
+ };
74
+ }
75
+ const baseImportance = memory.importance;
76
+ const ageDecay = calculateAgeDecay(memory.timestamp);
77
+ const accessBoost = calculateAccessBoost(memory_id);
78
+ const relationshipBoost = calculateRelationshipBoost(memory_id);
79
+ // Final score: base + boosts + decay (decay is negative)
80
+ const finalScore = Math.max(1, Math.min(5, baseImportance + accessBoost + relationshipBoost + ageDecay));
81
+ return {
82
+ baseImportance,
83
+ ageDecay,
84
+ accessBoost,
85
+ relationshipBoost,
86
+ finalScore: Math.round(finalScore * 10) / 10,
87
+ };
88
+ }
89
+ /**
90
+ * Get memories eligible for L1 based on dynamic scoring.
91
+ */
92
+ export function getL1Candidates(limit = 50) {
93
+ const db = getDb(true);
94
+ const memories = queries.getL1Summaries(db).all();
95
+ return memories.slice(0, limit).map(m => ({
96
+ id: m.id,
97
+ type: m.type,
98
+ content_l1: m.content_l1,
99
+ timestamp: m.timestamp,
100
+ last_accessed: m.last_accessed,
101
+ baseImportance: m.importance,
102
+ dynamicScore: m.effective_importance,
103
+ }));
104
+ }
105
+ /**
106
+ * Consolidation: Review and adjust importances based on access patterns
107
+ */
108
+ export function runConsolidation() {
109
+ const db = getDb(false);
110
+ let reviewed = 0;
111
+ let boosted = 0;
112
+ let decayed = 0;
113
+ let unchanged = 0;
114
+ const memories = db.prepare(`
115
+ SELECT id, importance, timestamp
116
+ FROM memory
117
+ WHERE timestamp > datetime('now', '-60 days')
118
+ `).all();
119
+ for (const memory of memories) {
120
+ reviewed++;
121
+ const factors = calculateDynamicImportance(memory.id);
122
+ const newImportance = Math.round(factors.finalScore);
123
+ if (newImportance !== memory.importance) {
124
+ db.prepare(`
125
+ UPDATE memory
126
+ SET importance = ?
127
+ WHERE id = ?
128
+ `).run(newImportance, memory.id);
129
+ if (newImportance > memory.importance) {
130
+ boosted++;
131
+ }
132
+ else {
133
+ decayed++;
134
+ }
135
+ }
136
+ else {
137
+ unchanged++;
138
+ }
139
+ }
140
+ return { reviewed, boosted, decayed, unchanged };
141
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@postnesia/db",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
4
4
  "description": "AI Agent memory context database",
5
5
  "type": "module",
6
6
  "private": false,
@@ -25,6 +25,14 @@
25
25
  "./embeddings": {
26
26
  "types": "./dist/embeddings.d.ts",
27
27
  "import": "./dist/embeddings.js"
28
+ },
29
+ "./access": {
30
+ "types": "./dist/access.d.ts",
31
+ "import": "./dist/access.js"
32
+ },
33
+ "./importance": {
34
+ "types": "./dist/importance.d.ts",
35
+ "import": "./dist/importance.js"
28
36
  }
29
37
  },
30
38
  "dependencies": {