@game_ryo/lsji 0.1.0 → 0.3.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.
Files changed (75) hide show
  1. package/package.json +15 -7
  2. package/src/cli.js +395 -62
  3. package/src/execution/budget/circuit-breaker.js +245 -0
  4. package/src/execution/budget/cost-tracker.js +387 -0
  5. package/src/execution/budget/index.js +63 -0
  6. package/src/execution/budget/token-counter.js +159 -0
  7. package/src/execution/engine.js +428 -0
  8. package/src/execution/hitl/approval-gate.js +210 -0
  9. package/src/execution/hitl/index.js +12 -0
  10. package/src/execution/hitl/notifier.js +151 -0
  11. package/src/execution/hitl/store.js +311 -0
  12. package/src/execution/idempotency.js +312 -0
  13. package/src/execution/index.js +14 -0
  14. package/src/index.js +80 -4
  15. package/src/llm/index.js +21 -0
  16. package/src/llm/llm-agent.js +357 -0
  17. package/src/llm/memory/conversation.js +271 -0
  18. package/src/llm/memory/episodic.js +312 -0
  19. package/src/llm/memory/index.js +12 -0
  20. package/src/llm/memory/semantic.js +324 -0
  21. package/src/llm/plugins/index.js +202 -0
  22. package/src/llm/prompt-manager.js +332 -0
  23. package/src/llm/providers/anthropic.js +250 -0
  24. package/src/llm/providers/base.js +116 -0
  25. package/src/llm/providers/local.js +163 -0
  26. package/src/llm/providers/openai.js +212 -0
  27. package/src/llm/tools/registry.js +342 -0
  28. package/src/server/index.js +416 -0
  29. package/src/server/ui/index.html +16 -0
  30. package/src/server/ui/package.json +19 -0
  31. package/src/server/ui/src/main.jsx +10 -0
  32. package/src/server/ui/src/styles.css +260 -0
  33. package/src/server/ui/vite.config.js +27 -0
  34. package/docs/README.md +0 -43
  35. package/docs/blog/2019-05-28-first-blog-post.mdx +0 -12
  36. package/docs/blog/2019-05-29-long-blog-post.mdx +0 -44
  37. package/docs/blog/2021-08-01-mdx-blog-post.mdx +0 -24
  38. package/docs/blog/2021-08-26-welcome/docusaurus-plushie-banner.jpeg +0 -0
  39. package/docs/blog/2021-08-26-welcome/index.mdx +0 -29
  40. package/docs/blog/authors.yml +0 -25
  41. package/docs/blog/tags.yml +0 -19
  42. package/docs/docs/api/agent.md +0 -151
  43. package/docs/docs/api/env.md +0 -133
  44. package/docs/docs/api/environments.md +0 -102
  45. package/docs/docs/api/qlearning.md +0 -138
  46. package/docs/docs/api/storage.md +0 -168
  47. package/docs/docs/architecture.md +0 -155
  48. package/docs/docs/cli.md +0 -210
  49. package/docs/docs/contributing.md +0 -162
  50. package/docs/docs/core-concepts.md +0 -152
  51. package/docs/docs/examples/advanced-training.md +0 -244
  52. package/docs/docs/examples/custom-environment.md +0 -198
  53. package/docs/docs/examples/custom-storage.md +0 -251
  54. package/docs/docs/getting-started.md +0 -91
  55. package/docs/docusaurus.config.ts +0 -149
  56. package/docs/package-lock.json +0 -19522
  57. package/docs/package.json +0 -49
  58. package/docs/sidebars.ts +0 -33
  59. package/docs/src/components/HomepageFeatures/index.tsx +0 -71
  60. package/docs/src/components/HomepageFeatures/styles.module.css +0 -11
  61. package/docs/src/css/custom.css +0 -79
  62. package/docs/src/pages/index.module.css +0 -23
  63. package/docs/src/pages/index.tsx +0 -44
  64. package/docs/src/pages/markdown-page.mdx +0 -7
  65. package/docs/static/.nojekyll +0 -0
  66. package/docs/static/img/docusaurus-social-card.jpg +0 -0
  67. package/docs/static/img/docusaurus.png +0 -0
  68. package/docs/static/img/favicon.ico +0 -0
  69. package/docs/static/img/logo.png +0 -0
  70. package/docs/static/img/undraw_docusaurus_mountain.svg +0 -171
  71. package/docs/static/img/undraw_docusaurus_react.svg +0 -170
  72. package/docs/static/img/undraw_docusaurus_tree.svg +0 -40
  73. package/docs/tsconfig.json +0 -12
  74. package/legacy/worker.js +0 -166
  75. package/legacy/wrangler.toml +0 -11
@@ -0,0 +1,312 @@
1
+ /**
2
+ * Episodic Memory
3
+ *
4
+ * Event-based memory for tracking agent actions and outcomes.
5
+ * Useful for learning from past experiences.
6
+ */
7
+
8
+ import { createStorage } from '../../index.js';
9
+
10
+ /**
11
+ * Episode record
12
+ * @typedef {Object} Episode
13
+ * @property {string} id - Unique ID
14
+ * @property {string} task - Task description
15
+ * @property {Array} steps - Steps taken
16
+ * @property {string} outcome - 'success' | 'failure' | 'partial'
17
+ * @property {Object} context - Initial context
18
+ * @property {Object} result - Final result
19
+ * @property {number} duration - Duration in ms
20
+ * @property {number} tokensUsed - Total tokens used
21
+ * @property {number} cost - Total cost
22
+ * @property {Date} startedAt
23
+ * @property {Date} completedAt
24
+ * @property {Array} [tags] - Tags for categorization
25
+ */
26
+
27
+ /**
28
+ * Episodic Memory - Event-based experience storage
29
+ */
30
+ export class EpisodicMemory {
31
+ constructor({ storage } = {}) {
32
+ this.storage = storage;
33
+ this.currentEpisode = null;
34
+ this.initialized = false;
35
+ }
36
+
37
+ /**
38
+ * Initialize episodes table
39
+ */
40
+ async initialize() {
41
+ if (this.initialized) return;
42
+
43
+ if (this.storage.db) {
44
+ await this.storage.db.exec(`
45
+ CREATE TABLE IF NOT EXISTS episodes (
46
+ id TEXT PRIMARY KEY,
47
+ task TEXT NOT NULL,
48
+ steps TEXT NOT NULL,
49
+ outcome TEXT NOT NULL,
50
+ context TEXT NOT NULL,
51
+ result TEXT,
52
+ duration INTEGER,
53
+ tokens_used INTEGER,
54
+ cost REAL,
55
+ started_at TEXT NOT NULL,
56
+ completed_at TEXT,
57
+ tags TEXT
58
+ )
59
+ `);
60
+
61
+ await this.storage.db.exec(`
62
+ CREATE INDEX IF NOT EXISTS idx_episodes_outcome ON episodes(outcome)
63
+ `);
64
+
65
+ await this.storage.db.exec(`
66
+ CREATE INDEX IF NOT EXISTS idx_episodes_started ON episodes(started_at)
67
+ `);
68
+
69
+ await this.storage.db.exec(`
70
+ CREATE INDEX IF NOT EXISTS idx_episodes_tags ON episodes(tags)
71
+ `);
72
+ }
73
+
74
+ this.initialized = true;
75
+ }
76
+
77
+ /**
78
+ * Start a new episode
79
+ */
80
+ async startEpisode(task, context = {}, tags = []) {
81
+ await this.initialize();
82
+
83
+ this.currentEpisode = {
84
+ id: `episode_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
85
+ task,
86
+ steps: [],
87
+ outcome: 'in_progress',
88
+ context,
89
+ result: null,
90
+ duration: 0,
91
+ tokensUsed: 0,
92
+ cost: 0,
93
+ startedAt: new Date().toISOString(),
94
+ completedAt: null,
95
+ tags,
96
+ };
97
+
98
+ return this.currentEpisode.id;
99
+ }
100
+
101
+ /**
102
+ * Add a step to current episode
103
+ */
104
+ async addStep(step) {
105
+ if (!this.currentEpisode) {
106
+ throw new Error('No active episode. Call startEpisode() first.');
107
+ }
108
+
109
+ this.currentEpisode.steps.push({
110
+ ...step,
111
+ timestamp: new Date().toISOString(),
112
+ });
113
+ }
114
+
115
+ /**
116
+ * End current episode
117
+ */
118
+ async endEpisode(outcome, result = null) {
119
+ if (!this.currentEpisode) {
120
+ throw new Error('No active episode. Call startEpisode() first.');
121
+ }
122
+
123
+ const completedAt = new Date().toISOString();
124
+ const startedAt = new Date(this.currentEpisode.startedAt);
125
+
126
+ this.currentEpisode.outcome = outcome;
127
+ this.currentEpisode.result = result;
128
+ this.currentEpisode.completedAt = completedAt;
129
+ this.currentEpisode.duration = new Date(completedAt).getTime() - startedAt.getTime();
130
+
131
+ // Persist
132
+ if (this.storage.db) {
133
+ await this.storage.db.run(
134
+ `INSERT INTO episodes (id, task, steps, outcome, context, result, duration, tokens_used, cost, started_at, completed_at, tags)
135
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
136
+ [
137
+ this.currentEpisode.id,
138
+ this.currentEpisode.task,
139
+ JSON.stringify(this.currentEpisode.steps),
140
+ this.currentEpisode.outcome,
141
+ JSON.stringify(this.currentEpisode.context),
142
+ JSON.stringify(this.currentEpisode.result),
143
+ this.currentEpisode.duration,
144
+ this.currentEpisode.tokensUsed,
145
+ this.currentEpisode.cost,
146
+ this.currentEpisode.startedAt,
147
+ this.currentEpisode.completedAt,
148
+ JSON.stringify(this.currentEpisode.tags),
149
+ ]
150
+ );
151
+ } else {
152
+ if (!this.memoryEpisodes) this.memoryEpisodes = new Map();
153
+ this.memoryEpisodes.set(this.currentEpisode.id, this.currentEpisode);
154
+ }
155
+
156
+ const episode = this.currentEpisode;
157
+ this.currentEpisode = null;
158
+ return episode;
159
+ }
160
+
161
+ /**
162
+ * Record token usage for current episode
163
+ */
164
+ recordTokens(tokens) {
165
+ if (this.currentEpisode) {
166
+ this.currentEpisode.tokensUsed += tokens;
167
+ }
168
+ }
169
+
170
+ /**
171
+ * Record cost for current episode
172
+ */
173
+ recordCost(cost) {
174
+ if (this.currentEpisode) {
175
+ this.currentEpisode.cost += cost;
176
+ }
177
+ }
178
+
179
+ /**
180
+ * Get episode by ID
181
+ */
182
+ async getEpisode(id) {
183
+ await this.initialize();
184
+
185
+ if (this.storage.db) {
186
+ const row = await this.storage.db.get('SELECT * FROM episodes WHERE id = ?', [id]);
187
+ if (!row) return null;
188
+ return this.rowToEpisode(row);
189
+ } else {
190
+ return this.memoryEpisodes?.get(id) || null;
191
+ }
192
+ }
193
+
194
+ /**
195
+ * Search episodes
196
+ */
197
+ async search({ outcome, tags = [], limit = 50, since = null } = {}) {
198
+ await this.initialize();
199
+
200
+ let results = [];
201
+
202
+ if (this.storage.db) {
203
+ let sql = 'SELECT * FROM episodes WHERE 1=1';
204
+ const params = [];
205
+
206
+ if (outcome) {
207
+ sql += ' AND outcome = ?';
208
+ params.push(outcome);
209
+ }
210
+
211
+ if (tags.length > 0) {
212
+ const tagConditions = tags.map(() => 'tags LIKE ?').join(' OR ');
213
+ sql += ` AND (${tagConditions})`;
214
+ for (const tag of tags) {
215
+ params.push(`%"${tag}"%`);
216
+ }
217
+ }
218
+
219
+ if (since) {
220
+ sql += ' AND started_at >= ?';
221
+ params.push(since.toISOString());
222
+ }
223
+
224
+ sql += ' ORDER BY started_at DESC LIMIT ?';
225
+ params.push(limit);
226
+
227
+ const rows = await this.storage.db.all(sql, params);
228
+ results = rows.map(r => this.rowToEpisode(r));
229
+ } else {
230
+ for (const ep of this.memoryEpisodes?.values() || []) {
231
+ if (outcome && ep.outcome !== outcome) continue;
232
+ if (tags.length > 0 && !tags.some(t => ep.tags.includes(t))) continue;
233
+ if (since && new Date(ep.startedAt) < since) continue;
234
+ results.push(ep);
235
+ }
236
+ results.sort((a, b) => new Date(b.startedAt) - new Date(a.startedAt));
237
+ results = results.slice(0, limit);
238
+ }
239
+
240
+ return results;
241
+ }
242
+
243
+ /**
244
+ * Get successful episodes for a task type
245
+ */
246
+ async getSuccessfulEpisodes(taskPattern, limit = 10) {
247
+ const episodes = await this.search({ outcome: 'success', limit: 100 });
248
+ return episodes
249
+ .filter(e => e.task.includes(taskPattern))
250
+ .slice(0, limit);
251
+ }
252
+
253
+ /**
254
+ * Get failure episodes for analysis
255
+ */
256
+ async getFailures({ limit = 50, since = null } = {}) {
257
+ return this.search({ outcome: 'failure', limit, since });
258
+ }
259
+
260
+ /**
261
+ * Get episode statistics
262
+ */
263
+ async getStats() {
264
+ await this.initialize();
265
+
266
+ if (this.storage.db) {
267
+ const row = await this.storage.db.get(`
268
+ SELECT
269
+ COUNT(*) as total,
270
+ SUM(CASE WHEN outcome = 'success' THEN 1 ELSE 0 END) as successes,
271
+ SUM(CASE WHEN outcome = 'failure' THEN 1 ELSE 0 END) as failures,
272
+ SUM(CASE WHEN outcome = 'partial' THEN 1 ELSE 0 END) as partials,
273
+ AVG(duration) as avg_duration,
274
+ AVG(tokens_used) as avg_tokens,
275
+ AVG(cost) as avg_cost
276
+ FROM episodes
277
+ `);
278
+ return row;
279
+ }
280
+
281
+ return { total: 0, successes: 0, failures: 0, partials: 0 };
282
+ }
283
+
284
+ rowToEpisode(row) {
285
+ return {
286
+ id: row.id,
287
+ task: row.task,
288
+ steps: JSON.parse(row.steps || '[]'),
289
+ outcome: row.outcome,
290
+ context: JSON.parse(row.context || '{}'),
291
+ result: row.result ? JSON.parse(row.result) : null,
292
+ duration: row.duration,
293
+ tokensUsed: row.tokens_used,
294
+ cost: row.cost,
295
+ startedAt: row.started_at,
296
+ completedAt: row.completed_at,
297
+ tags: JSON.parse(row.tags || '[]'),
298
+ };
299
+ }
300
+ }
301
+
302
+ /**
303
+ * Create episodic memory from config
304
+ */
305
+ export async function createEpisodicMemory(config = {}) {
306
+ const storage = await createStorage(
307
+ config.storage?.type || 'sqlite',
308
+ config.storage?.options || {}
309
+ );
310
+
311
+ return new EpisodicMemory({ storage });
312
+ }
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Memory Systems
3
+ *
4
+ * Exports all memory components:
5
+ * - ConversationMemory: Short-term conversation history
6
+ * - SemanticMemory: Long-term knowledge with retrieval
7
+ * - EpisodicMemory: Event-based experience tracking
8
+ */
9
+
10
+ export { ConversationMemory, createConversationMemory } from './conversation.js';
11
+ export { SemanticMemory, createSemanticMemory } from './semantic.js';
12
+ export { EpisodicMemory, createEpisodicMemory } from './episodic.js';
@@ -0,0 +1,324 @@
1
+ /**
2
+ * Semantic Memory
3
+ *
4
+ * Long-term knowledge storage with vector embeddings for retrieval.
5
+ * Uses simple keyword-based retrieval (can be extended with vector DB).
6
+ */
7
+
8
+ import { createStorage } from '../../index.js';
9
+
10
+ /**
11
+ * Knowledge entry
12
+ * @typedef {Object} KnowledgeEntry
13
+ * @property {string} id - Unique ID
14
+ * @property {string} content - Knowledge content
15
+ * @property {Array<string>} tags - Tags for categorization
16
+ * @property {Object} metadata - Additional metadata
17
+ * @property {number} [embedding] - Vector embedding (placeholder)
18
+ * @property {Date} createdAt
19
+ * @property {Date} updatedAt
20
+ * @property {number} accessCount - Number of times accessed
21
+ */
22
+
23
+ /**
24
+ * Semantic Memory - Long-term knowledge storage
25
+ */
26
+ export class SemanticMemory {
27
+ constructor({ storage, embedder } = {}) {
28
+ this.storage = storage;
29
+ this.embedder = embedder; // Function to generate embeddings
30
+ this.initialized = false;
31
+ }
32
+
33
+ /**
34
+ * Initialize knowledge table
35
+ */
36
+ async initialize() {
37
+ if (this.initialized) return;
38
+
39
+ if (this.storage.db) {
40
+ await this.storage.db.exec(`
41
+ CREATE TABLE IF NOT EXISTS knowledge (
42
+ id TEXT PRIMARY KEY,
43
+ content TEXT NOT NULL,
44
+ tags TEXT NOT NULL,
45
+ metadata TEXT,
46
+ embedding TEXT,
47
+ created_at TEXT NOT NULL,
48
+ updated_at TEXT NOT NULL,
49
+ access_count INTEGER DEFAULT 0
50
+ )
51
+ `);
52
+
53
+ await this.storage.db.exec(`
54
+ CREATE INDEX IF NOT EXISTS idx_knowledge_tags ON knowledge(tags)
55
+ `);
56
+
57
+ await this.storage.db.exec(`
58
+ CREATE INDEX IF NOT EXISTS idx_knowledge_created ON knowledge(created_at)
59
+ `);
60
+ }
61
+
62
+ this.initialized = true;
63
+ }
64
+
65
+ /**
66
+ * Store knowledge
67
+ */
68
+ async store(content, { tags = [], metadata = {}, id = null } = {}) {
69
+ await this.initialize();
70
+
71
+ const entryId = id || `knowledge_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
72
+ const now = new Date().toISOString();
73
+
74
+ let embedding = null;
75
+ if (this.embedder) {
76
+ embedding = await this.embedder(content);
77
+ }
78
+
79
+ const entry = {
80
+ id: entryId,
81
+ content,
82
+ tags: JSON.stringify(tags),
83
+ metadata: JSON.stringify(metadata),
84
+ embedding: embedding ? JSON.stringify(embedding) : null,
85
+ createdAt: now,
86
+ updatedAt: now,
87
+ accessCount: 0,
88
+ };
89
+
90
+ if (this.storage.db) {
91
+ await this.storage.db.run(
92
+ `INSERT INTO knowledge (id, content, tags, metadata, embedding, created_at, updated_at, access_count)
93
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
94
+ [entry.id, entry.content, entry.tags, entry.metadata, entry.embedding, entry.createdAt, entry.updatedAt, entry.accessCount]
95
+ );
96
+ } else {
97
+ if (!this.memoryKnowledge) this.memoryKnowledge = new Map();
98
+ this.memoryKnowledge.set(entryId, entry);
99
+ }
100
+
101
+ return entry;
102
+ }
103
+
104
+ /**
105
+ * Retrieve knowledge by query
106
+ */
107
+ async retrieve(query, { limit = 10, tags = [] } = {}) {
108
+ await this.initialize();
109
+
110
+ let results = [];
111
+
112
+ if (this.storage.db) {
113
+ let sql = 'SELECT * FROM knowledge WHERE 1=1';
114
+ const params = [];
115
+
116
+ // Add tag filter
117
+ if (tags.length > 0) {
118
+ const tagConditions = tags.map(() => 'tags LIKE ?').join(' OR ');
119
+ sql += ` AND (${tagConditions})`;
120
+ for (const tag of tags) {
121
+ params.push(`%"${tag}"%`);
122
+ }
123
+ }
124
+
125
+ sql += ' ORDER BY access_count DESC, created_at DESC LIMIT ?';
126
+ params.push(limit);
127
+
128
+ const rows = await this.storage.db.all(sql, params);
129
+
130
+ for (const row of rows) {
131
+ results.push(this.rowToEntry(row));
132
+ }
133
+ } else {
134
+ for (const entry of this.memoryKnowledge?.values() || []) {
135
+ if (tags.length === 0 || tags.some(t => entry.tags.includes(t))) {
136
+ results.push(entry);
137
+ }
138
+ }
139
+ results.sort((a, b) => b.accessCount - a.accessCount);
140
+ results = results.slice(0, limit);
141
+ }
142
+
143
+ // Simple keyword relevance scoring
144
+ const queryWords = query.toLowerCase().split(/\s+/);
145
+ for (const entry of results) {
146
+ const contentWords = entry.content.toLowerCase().split(/\s+/);
147
+ let score = 0;
148
+ for (const qw of queryWords) {
149
+ for (const cw of contentWords) {
150
+ if (cw.includes(qw) || qw.includes(cw)) score++;
151
+ }
152
+ }
153
+ entry.relevanceScore = score;
154
+ }
155
+
156
+ // Sort by relevance
157
+ results.sort((a, b) => (b.relevanceScore || 0) - (a.relevanceScore || 0));
158
+
159
+ // Update access count
160
+ for (const entry of results) {
161
+ await this.incrementAccess(entry.id);
162
+ }
163
+
164
+ return results;
165
+ }
166
+
167
+ /**
168
+ * Get knowledge by ID
169
+ */
170
+ async get(id) {
171
+ await this.initialize();
172
+
173
+ if (this.storage.db) {
174
+ const row = await this.storage.db.get('SELECT * FROM knowledge WHERE id = ?', [id]);
175
+ if (!row) return null;
176
+ return this.rowToEntry(row);
177
+ } else {
178
+ return this.memoryKnowledge?.get(id) || null;
179
+ }
180
+ }
181
+
182
+ /**
183
+ * Update knowledge
184
+ */
185
+ async update(id, { content, tags, metadata } = {}) {
186
+ await this.initialize();
187
+
188
+ const entry = await this.get(id);
189
+ if (!entry) throw new Error(`Knowledge not found: ${id}`);
190
+
191
+ const updates = [];
192
+ const params = [];
193
+
194
+ if (content !== undefined) {
195
+ entry.content = content;
196
+ updates.push('content = ?');
197
+ params.push(content);
198
+
199
+ if (this.embedder) {
200
+ entry.embedding = JSON.stringify(await this.embedder(content));
201
+ updates.push('embedding = ?');
202
+ params.push(entry.embedding);
203
+ }
204
+ }
205
+
206
+ if (tags !== undefined) {
207
+ entry.tags = JSON.stringify(tags);
208
+ updates.push('tags = ?');
209
+ params.push(entry.tags);
210
+ }
211
+
212
+ if (metadata !== undefined) {
213
+ entry.metadata = JSON.stringify({ ...JSON.parse(entry.metadata), ...metadata });
214
+ updates.push('metadata = ?');
215
+ params.push(entry.metadata);
216
+ }
217
+
218
+ entry.updatedAt = new Date().toISOString();
219
+ updates.push('updated_at = ?');
220
+ params.push(entry.updatedAt);
221
+
222
+ params.push(id);
223
+
224
+ if (this.storage.db) {
225
+ await this.storage.db.run(
226
+ `UPDATE knowledge SET ${updates.join(', ')} WHERE id = ?`,
227
+ params
228
+ );
229
+ } else {
230
+ this.memoryKnowledge.set(id, entry);
231
+ }
232
+
233
+ return entry;
234
+ }
235
+
236
+ /**
237
+ * Delete knowledge
238
+ */
239
+ async delete(id) {
240
+ await this.initialize();
241
+
242
+ if (this.storage.db) {
243
+ await this.storage.db.run('DELETE FROM knowledge WHERE id = ?', [id]);
244
+ } else {
245
+ this.memoryKnowledge?.delete(id);
246
+ }
247
+ }
248
+
249
+ /**
250
+ * Increment access count
251
+ */
252
+ async incrementAccess(id) {
253
+ await this.initialize();
254
+
255
+ if (this.storage.db) {
256
+ await this.storage.db.run(
257
+ 'UPDATE knowledge SET access_count = access_count + 1 WHERE id = ?',
258
+ [id]
259
+ );
260
+ } else {
261
+ const entry = this.memoryKnowledge?.get(id);
262
+ if (entry) {
263
+ entry.accessCount++;
264
+ this.memoryKnowledge.set(id, entry);
265
+ }
266
+ }
267
+ }
268
+
269
+ /**
270
+ * List all knowledge
271
+ */
272
+ async list({ limit = 100, tags = [] } = {}) {
273
+ await this.initialize();
274
+
275
+ if (this.storage.db) {
276
+ let sql = 'SELECT * FROM knowledge WHERE 1=1';
277
+ const params = [];
278
+
279
+ if (tags.length > 0) {
280
+ const tagConditions = tags.map(() => 'tags LIKE ?').join(' OR ');
281
+ sql += ` AND (${tagConditions})`;
282
+ for (const tag of tags) {
283
+ params.push(`%"${tag}"%`);
284
+ }
285
+ }
286
+
287
+ sql += ' ORDER BY created_at DESC LIMIT ?';
288
+ params.push(limit);
289
+
290
+ const rows = await this.storage.db.all(sql, params);
291
+ return rows.map(r => this.rowToEntry(r));
292
+ }
293
+
294
+ return [];
295
+ }
296
+
297
+ rowToEntry(row) {
298
+ return {
299
+ id: row.id,
300
+ content: row.content,
301
+ tags: JSON.parse(row.tags || '[]'),
302
+ metadata: JSON.parse(row.metadata || '{}'),
303
+ embedding: row.embedding ? JSON.parse(row.embedding) : null,
304
+ createdAt: row.created_at,
305
+ updatedAt: row.updated_at,
306
+ accessCount: row.access_count || 0,
307
+ };
308
+ }
309
+ }
310
+
311
+ /**
312
+ * Create semantic memory from config
313
+ */
314
+ export async function createSemanticMemory(config = {}) {
315
+ const storage = await createStorage(
316
+ config.storage?.type || 'sqlite',
317
+ config.storage?.options || {}
318
+ );
319
+
320
+ return new SemanticMemory({
321
+ storage,
322
+ embedder: config.embedder,
323
+ });
324
+ }