@agent-diaries/core 1.0.0 → 1.1.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/README.md CHANGED
@@ -29,11 +29,12 @@ Install the core package:
29
29
  npm install @agent-diaries/core
30
30
  ```
31
31
 
32
- If you plan to use a specific cloud adapter, install its peer dependency:
32
+ If you plan to use a specific storage adapter, install its peer dependency:
33
33
 
34
34
  ```bash
35
- npm install ioredis # For Redis Storage
36
- npm install mongodb # For MongoDB Storage
35
+ npm install better-sqlite3 # For SQLite Storage
36
+ npm install ioredis # For Redis Storage
37
+ npm install mongodb # For MongoDB Storage
37
38
  ```
38
39
 
39
40
  ## 🚀 Quick Start
@@ -99,11 +100,26 @@ const updatedResult = "Found 0 warnings, ALL critical errors resolved.";
99
100
  await diary.writeTaskResult(currentTask, updatedResult);
100
101
  ```
101
102
 
102
- ## ☁️ Cloud Storage Adapters (Production)
103
+ ## 🗄️ Storage Adapters (Cloud & Local Databases)
103
104
 
104
- Local file storage is great for local development, but serverless environments (Vercel, AWS Lambda) have ephemeral filesystems. For production, you **must** use one of our lock-safe cloud adapters.
105
+ Local file storage is great for local development, but serverless environments (Vercel, AWS Lambda) have ephemeral filesystems and require lock-safe cloud adapters, while local tools and desktops benefit from relational SQLite coordination.
105
106
 
106
- ### Redis (Best for Serverless)
107
+ ### SQLite (Best for Desktop / Local Apps)
108
+ The `SqliteStorage` adapter uses a local SQLite database (`better-sqlite3`) with atomic UNIQUE constraint insertions and transactional TTL locks for highly reliable multi-process coordination.
109
+
110
+ ```typescript
111
+ import { AgentDiary } from '@agent-diaries/core';
112
+ import { SqliteStorage } from '@agent-diaries/core/dist/adapters/sqlite';
113
+ import Database from 'better-sqlite3';
114
+
115
+ const db = new Database('diary.db');
116
+ const diary = new AgentDiary({
117
+ agentId: 'sqlite-bot',
118
+ storage: new SqliteStorage({ db })
119
+ });
120
+ ```
121
+
122
+ ### Redis (Best for Serverless / Swarms)
107
123
  The `RedisStorage` adapter uses atomic `SETNX` distributed spin-locks to guarantee race-condition safety across thousands of concurrent Vercel Edge functions.
108
124
 
109
125
  ```typescript
@@ -135,13 +151,32 @@ const diary = new AgentDiary({
135
151
  });
136
152
  ```
137
153
 
138
- ## 📊 200-Agent Real-World Cloud Benchmarks
154
+ ## 📊 Enterprise Concurrency Benchmarks
139
155
 
140
156
  Agent Diaries Core is mathematically proven to handle massive concurrent agent swarms without race conditions or database corruption.
141
157
 
142
- To prove its viability for enterprise serverless deployments, we rigorously stress-tested the library against a **Live Cloud Upstash Redis Database**, blasting it with **200 serverless agents** executing distributed lock requests across the internet at the exact same millisecond.
158
+ ### 1. Multi-Process OS-Level Concurrency (Worker Threads)
159
+ To verify true operating-system level process isolation, we spawned 50 independent Node.js `worker_threads` to aggressively hit the cloud databases at the exact same millisecond.
160
+
161
+ ```text
162
+ 🌪️ Spawning 50 Multi-Process Workers for REDIS...
163
+ Expected Locks: 1
164
+ Actual Locks: 1
165
+ Resolution Time: ~4300ms
166
+ 🟢 PASSED (49 race conditions prevented across OS processes)
167
+
168
+ 🌪️ Spawning 50 Multi-Process Workers for MONGO...
169
+ Expected Locks: 1
170
+ Actual Locks: 1
171
+ Resolution Time: 8192ms
172
+ 🟢 PASSED (49 race conditions prevented across OS processes)
173
+ ```
174
+
175
+ ### 2. 200-Agent Real-World Cloud Scale
176
+
177
+ To prove its viability for global serverless deployments, we rigorously stress-tested the library against live instances, blasting them with **200 serverless agents** executing distributed lock requests across the internet simultaneously.
143
178
 
144
- ### The Real-Life Architecture
179
+ #### The Real-Life Architecture
145
180
  ```typescript
146
181
  const NUM_AGENTS = 200;
147
182
  let agents = Array.from({ length: NUM_AGENTS }, () => getDiary());
@@ -0,0 +1,16 @@
1
+ import { StorageAdapter } from '../storage';
2
+ import type { Database } from 'better-sqlite3';
3
+ export interface SqliteStorageOptions {
4
+ db: Database;
5
+ tableName?: string;
6
+ locksTableName?: string;
7
+ }
8
+ export declare class SqliteStorage<T> implements StorageAdapter<T> {
9
+ private db;
10
+ private tableName;
11
+ private locksTableName;
12
+ constructor(options: SqliteStorageOptions);
13
+ get(key: string): Promise<T | null>;
14
+ set(key: string, value: T): Promise<void>;
15
+ withLock<R>(key: string, fn: () => Promise<R>): Promise<R>;
16
+ }
@@ -0,0 +1,126 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SqliteStorage = void 0;
4
+ class SqliteStorage {
5
+ db;
6
+ tableName;
7
+ locksTableName;
8
+ constructor(options) {
9
+ if (!options.db) {
10
+ throw new Error('[SqliteStorage] database instance (db) is required.');
11
+ }
12
+ this.db = options.db;
13
+ this.tableName = options.tableName || 'agent_diaries_storage';
14
+ this.locksTableName = options.locksTableName || 'agent_diaries_locks';
15
+ // Initialize tables synchronously as better-sqlite3 is fully synchronous
16
+ this.db.exec(`
17
+ CREATE TABLE IF NOT EXISTS ${this.tableName} (
18
+ key TEXT PRIMARY KEY,
19
+ value TEXT
20
+ );
21
+ CREATE TABLE IF NOT EXISTS ${this.locksTableName} (
22
+ key TEXT PRIMARY KEY,
23
+ locked_at INTEGER
24
+ );
25
+ `);
26
+ }
27
+ async get(key) {
28
+ try {
29
+ const row = this.db
30
+ .prepare(`SELECT value FROM ${this.tableName} WHERE key = ?`)
31
+ .get(key);
32
+ if (!row)
33
+ return null;
34
+ return JSON.parse(row.value);
35
+ }
36
+ catch (e) {
37
+ console.error(`[SqliteStorage] Failed to get key ${key}:`, e);
38
+ return null;
39
+ }
40
+ }
41
+ async set(key, value) {
42
+ try {
43
+ const serialized = JSON.stringify(value);
44
+ this.db
45
+ .prepare(`
46
+ INSERT INTO ${this.tableName} (key, value)
47
+ VALUES (?, ?)
48
+ ON CONFLICT(key) DO UPDATE SET value = excluded.value
49
+ `)
50
+ .run(key, serialized);
51
+ }
52
+ catch (e) {
53
+ console.error(`[SqliteStorage] Failed to set key ${key}:`, e);
54
+ throw e;
55
+ }
56
+ }
57
+ async withLock(key, fn) {
58
+ const lockKey = `lock:${key}`;
59
+ const lockTtlMs = 10000; // 10 seconds max lock
60
+ const acquireLock = () => {
61
+ const now = Date.now();
62
+ try {
63
+ this.db
64
+ .prepare(`INSERT INTO ${this.locksTableName} (key, locked_at) VALUES (?, ?)`)
65
+ .run(lockKey, now);
66
+ return true;
67
+ }
68
+ catch (error) {
69
+ if (error.code === 'SQLITE_CONSTRAINT_PRIMARYKEY' ||
70
+ error.code === 'SQLITE_CONSTRAINT' ||
71
+ (error.message && error.message.includes('UNIQUE constraint failed'))) {
72
+ // Check if the lock has expired
73
+ const existing = this.db
74
+ .prepare(`SELECT locked_at FROM ${this.locksTableName} WHERE key = ?`)
75
+ .get(lockKey);
76
+ if (existing && now - existing.locked_at > lockTtlMs) {
77
+ // Attempt to clear the expired lock and acquire it
78
+ try {
79
+ // Wrap cleanup and acquire in a transaction for atomicity
80
+ const runCleanupAndAcquire = this.db.transaction(() => {
81
+ this.db
82
+ .prepare(`DELETE FROM ${this.locksTableName} WHERE key = ? AND locked_at = ?`)
83
+ .run(lockKey, existing.locked_at);
84
+ this.db
85
+ .prepare(`INSERT INTO ${this.locksTableName} (key, locked_at) VALUES (?, ?)`)
86
+ .run(lockKey, now);
87
+ });
88
+ runCleanupAndAcquire();
89
+ return true;
90
+ }
91
+ catch (retryError) {
92
+ // Conflict during retry, lock is still held or acquired by another concurrent process
93
+ return false;
94
+ }
95
+ }
96
+ return false;
97
+ }
98
+ throw error;
99
+ }
100
+ };
101
+ let attempt = 0;
102
+ while (!acquireLock()) {
103
+ const backoff = Math.min(10 * Math.pow(2, attempt), 500);
104
+ const jitter = Math.random() * 50;
105
+ await new Promise(resolve => setTimeout(resolve, backoff + jitter));
106
+ attempt++;
107
+ if (attempt > 60) {
108
+ throw new Error(`[SqliteStorage] Lock timeout on key: ${key}`);
109
+ }
110
+ }
111
+ try {
112
+ return await fn();
113
+ }
114
+ finally {
115
+ try {
116
+ this.db
117
+ .prepare(`DELETE FROM ${this.locksTableName} WHERE key = ?`)
118
+ .run(lockKey);
119
+ }
120
+ catch (e) {
121
+ console.error(`[SqliteStorage] Failed to release lock on key ${key}:`, e);
122
+ }
123
+ }
124
+ }
125
+ }
126
+ exports.SqliteStorage = SqliteStorage;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-diaries/core",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "The lightweight, framework-agnostic memory layer for edge AI agents.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -9,7 +9,7 @@
9
9
  ],
10
10
  "scripts": {
11
11
  "test": "vitest run",
12
- "test:coverage": "vitest run --coverage",
12
+ "test:coverage": "vitest run --coverage.reporter=lcov --coverage.reporter=text",
13
13
  "build": "tsc",
14
14
  "prepublishOnly": "npm run build"
15
15
  },
@@ -35,23 +35,31 @@
35
35
  "homepage": "https://github.com/swapwarick/agent-diaries-core#readme",
36
36
  "license": "MIT",
37
37
  "devDependencies": {
38
- "dotenv": "^17.4.2",
38
+ "@types/better-sqlite3": "^7.6.11",
39
39
  "@types/dotenv": "^6.1.1",
40
40
  "@types/node": "^20.0.0",
41
41
  "@types/proper-lockfile": "^4.1.4",
42
42
  "@vitest/coverage-v8": "^4.1.5",
43
+ "dotenv": "^17.4.2",
44
+ "ioredis": "^5.5.0",
45
+ "mongodb": "^6.13.0",
43
46
  "ts-node": "^10.9.2",
44
47
  "typescript": "^5.0.0",
45
48
  "vitest": "^4.1.5"
46
49
  },
47
50
  "dependencies": {
51
+ "mempalace-agent": "^1.0.0",
48
52
  "proper-lockfile": "^4.1.2"
49
53
  },
50
54
  "peerDependencies": {
55
+ "better-sqlite3": ">=9 || >=10 || >=11",
51
56
  "ioredis": ">=5",
52
57
  "mongodb": ">=6"
53
58
  },
54
59
  "peerDependenciesMeta": {
60
+ "better-sqlite3": {
61
+ "optional": true
62
+ },
55
63
  "ioredis": {
56
64
  "optional": true
57
65
  },
@@ -59,4 +67,4 @@
59
67
  "optional": true
60
68
  }
61
69
  }
62
- }
70
+ }