@game_ryo/lsji 1.1.0 → 1.2.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/AGENTS.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  **LSJI** (Learning System for JavaScript Intelligence) is a general-purpose Reinforcement Learning agent framework for Node.js. It provides a clean abstraction for building RL agents with pluggable storage backends, environments, and learning algorithms.
6
6
 
7
- Originally migrated from a Cloudflare Workers implementation (Rock-Paper-Scissors AI), now redesigned as a standalone npm package with Apache 2.0 license, targeting Apache Incubator entry.
7
+ Originally migrated from a Cloudflare Workers implementation (Rock-Paper-Scissors AI), now redesigned as a standalone npm package with MIT license, targeting MIT licensed.
8
8
 
9
9
  ## Architecture
10
10
 
@@ -19,6 +19,23 @@ src/
19
19
  │ ├── sqlite.js # node:sqlite implementation (Node 22+)
20
20
  │ ├── better-sqlite.js # better-sqlite3 implementation
21
21
  │ └── memory.js # In-memory implementation
22
+ ├── execution/ # Production execution systems
23
+ │ ├── budget/ # TokenCounter, CostTracker, CircuitBreaker
24
+ │ ├── hitl/ # ApprovalGate, ApprovalStore, Notifier
25
+ │ ├── idempotency.js # Duplicate prevention
26
+ │ └── engine.js # Durable execution with checkpoints
27
+ ├── llm/ # LLM Agent Framework
28
+ │ ├── providers/ # OpenAI, Anthropic, Gemini, Local (Ollama)
29
+ │ ├── llm-agent.js # ReAct agent with tools & memory
30
+ │ ├── tools/ # Built-in tools (web_search, file_read, file_write, code_exec, api_call, send_email, db_query)
31
+ │ ├── memory/ # Conversation, Semantic, Episodic
32
+ │ ├── plugins/ # Dynamic plugin system
33
+ │ └── prompt-manager.js
34
+ ├── server/ # Runtime server + Web UI
35
+ │ ├── index.js # Express + Socket.io
36
+ │ └── ui/ # React + Vite control panel
37
+ ├── envs/
38
+ │ └── rps.js # Rock-Paper-Scissors environment
22
39
  ├── cli.js # Command-line interface
23
40
  └── index.js # Public API exports
24
41
  ```
@@ -59,9 +76,9 @@ Pluggable backends:
59
76
 
60
77
  ## Usage
61
78
 
62
- ### As Library
79
+ ### As Library (RL)
63
80
  ```javascript
64
- import { Agent, QLearning, createStorage, Env } from 'lsji';
81
+ import { Agent, QLearning, createStorage, Env } from '@game_ryo/lsji';
65
82
 
66
83
  // Create custom environment
67
84
  class MyEnv extends Env {
@@ -77,23 +94,60 @@ await agent.train({ episodes: 1000 });
77
94
  const result = await agent.play(userAction);
78
95
  ```
79
96
 
97
+ ### As Library (LLM Agent)
98
+ ```javascript
99
+ import { createLLMAgent, startServer } from '@game_ryo/lsji';
100
+
101
+ // Create an agent with full production features
102
+ const agent = await createLLMAgent({
103
+ llm: { provider: 'gemini', model: 'gemini-1.5-flash', apiKey: process.env.GEMINI_API_KEY },
104
+ budget: { maxCostPerRun: 5, maxCostPerDay: 50 },
105
+ hitl: { enabled: true, defaultTimeout: 300000 }, // 5 min approval timeout
106
+ memory: { conversation: true, episodic: true, semantic: true },
107
+ storage: { type: 'sqlite', options: { path: './lsji.db' } },
108
+ });
109
+
110
+ const result = await agent.run("Research TypeScript best practices and create a summary");
111
+ console.log(result.answer);
112
+
113
+ // Or start the full runtime server
114
+ const server = await startServer({ port: 3456 });
115
+ // Visit http://localhost:3456 for the web control panel
116
+ ```
117
+
80
118
  ### CLI
81
119
  ```bash
82
120
  # Install globally or use npx
83
121
  npm link # for local development
84
122
 
85
- # Train
86
- lsji train --episodes 500 --pattern 0
87
-
88
- # Play
89
- lsji play --hand 0 # 0=Rock, 1=Scissors, 2=Paper
90
-
91
- # Status
92
- lsji status --json
93
-
94
- # Control
95
- lsji start
96
- lsji stop
123
+ # RL Commands
124
+ npx @game_ryo/lsji train --episodes 500 --pattern 0
125
+ npx @game_ryo/lsji play --hand 0 # 0=Rock, 1=Scissors, 2=Paper
126
+ npx @game_ryo/lsji status --json
127
+ npx @game_ryo/lsji start
128
+ npx @game_ryo/lsji stop
129
+
130
+ # LLM Agent Commands
131
+ npx @game_ryo/lsji agent run --task "Search for latest AI news and summarize" --provider gemini --model gemini-1.5-flash
132
+ npx @game_ryo/lsji agent run-durable --task "Analyze codebase and create report" --workflow-id my-analysis
133
+ npx @game_ryo/lsji agent status
134
+
135
+ # Budget & Approvals
136
+ npx @game_ryo/lsji budget status --budgetId my-project
137
+ npx @game_ryo/lsji hitl list # Pending approvals
138
+ npx @game_ryo/lsji hitl approve --id <id> --reason "Approved"
139
+
140
+ # Durability
141
+ npx @game_ryo/lsji checkpoint list
142
+ npx @game_ryo/lsji checkpoint show --workflowId my-analysis
143
+ npx @game_ryo/lsji checkpoint recover --workflowId my-analysis
144
+
145
+ # Plugins
146
+ npx @game_ryo/lsji plugin list
147
+ npx @game_ryo/lsji plugin create --name my-tools
148
+
149
+ # Server
150
+ npx @game_ryo/lsji serve --port 3456
97
151
  ```
98
152
 
99
153
  ## Development
@@ -102,20 +156,20 @@ lsji stop
102
156
  ```bash
103
157
  npm test # Run tests (vitest)
104
158
  npm run build # No build step (ESM)
159
+ npm run build:ui # Build web UI
160
+ npm run serve # Start runtime server
105
161
  ```
106
162
 
107
163
  ### Testing
108
- - Tests use `vitest` with `MemoryStorage` for isolation
109
- - Run `npm test` to verify all functionality
110
-
111
- ## Key Design Decisions
164
+ - Tests use `vitest` with `Memo## Key Design Decisions
112
165
 
113
166
  1. **ESM only** - Uses `"type": "module"` in package.json
114
167
  2. **Node 22+** - Requires Node 22 for built-in `node:sqlite`
115
- 3. **Apache 2.0** - License compatible with Apache Incubator
168
+ 3. **MIT** - License compatible with open source
116
169
  4. **Single package** - All core functionality in one npm package
117
170
  5. **Storage abstraction** - Easy to swap backends
118
171
  6. **Worker.js compatibility** - Training patterns and reward logic match original
172
+ 7. **Production-grade** - HITL, durability, budget controls, idempotency built-in
119
173
 
120
174
  ## Common Tasks
121
175
 
@@ -129,6 +183,11 @@ npm run build # No build step (ESM)
129
183
  2. Implement all abstract methods
130
184
  3. Add to `createStorage` factory
131
185
 
186
+ ### Adding a New LLM Tool
187
+ 1. Create tool definition in `src/llm/tools/registry.js` or plugin
188
+ 2. Implement `execute` function
189
+ 3. Add to tool registry
190
+
132
191
  ### Modifying Learning Algorithm
133
192
  - Extend `QLearning` class or create new algorithm in `src/core/`
134
193
 
@@ -140,4 +199,14 @@ npm run build # No build step (ESM)
140
199
 
141
200
  ## License
142
201
 
143
- Apache 2.0 - see LICENSE file
202
+ MIT - see LICENSE file
203
+
204
+ ## Git Workflow
205
+
206
+ - Commit messages in English
207
+ - Format: `<type>: <subject>` (e.g., `feat: add new environment base class`)
208
+ - Types: feat, fix, docs, refactor, test, chore
209
+
210
+ ## License
211
+
212
+ MIT - see LICENSE file
package/README.md CHANGED
@@ -25,14 +25,14 @@
25
25
 
26
26
  ```bash
27
27
  # Install
28
- npm install lsji
28
+ npm install @game_ryo/lsji
29
29
 
30
30
  # Set your API key (Gemini, OpenAI, or Anthropic)
31
31
  export GEMINI_API_KEY="your-gemini-key"
32
32
  # or: export OPENAI_API_KEY="your-openai-key"
33
33
 
34
34
  # Start the runtime server with web UI
35
- npx lsji serve
35
+ npx @game_ryo/npx @game_ryo/lsji serve
36
36
 
37
37
  # Open http://localhost:3456 — create runs, watch thought logs, approve actions
38
38
  ```
@@ -41,25 +41,25 @@ npx lsji serve
41
41
 
42
42
  ```bash
43
43
  # Runtime server with control panel
44
- lsji serve --port 3456 # Start server + UI
45
- lsji serve --no-ui # Headless mode
44
+ npx @game_ryo/lsji serve --port 3456 # Start server + UI
45
+ npx @game_ryo/lsji serve --no-ui # Headless mode
46
46
 
47
47
  # Agent operations
48
- lsji agent run --task "Write a report on AI trends" --provider gemini --model gemini-1.5-flash
49
- lsji agent run-durable --task "Analyze codebase" --workflow-id my-analysis
48
+ npx @game_ryo/lsji agent run --task "Write a report on AI trends" --provider gemini --model gemini-1.5-flash
49
+ npx @game_ryo/lsji agent run-durable --task "Analyze codebase" --workflow-id my-analysis
50
50
 
51
51
  # Budget & approvals
52
- lsji budget status --budgetId my-project
53
- lsji hitl list # Pending approvals
54
- lsji hitl approve --id <id> --reason "Approved"
52
+ npx @game_ryo/lsji budget status --budgetId my-project
53
+ npx @game_ryo/lsji hitl list # Pending approvals
54
+ npx @game_ryo/lsji hitl approve --id <id> --reason "Approved"
55
55
 
56
56
  # Plugins
57
- lsji plugin list
58
- lsji plugin create --name my-tools # Generate template
57
+ npx @game_ryo/lsji plugin list
58
+ npx @game_ryo/lsji plugin create --name my-tools # Generate template
59
59
 
60
60
  # RL (legacy)
61
- lsji train --episodes 500
62
- lsji play --hand 0
61
+ npx @game_ryo/lsji train --episodes 500
62
+ npx @game_ryo/lsji play --hand 0
63
63
  ```
64
64
 
65
65
  ## Programmatic Usage
@@ -85,7 +85,7 @@ const server = await startServer({ port: 3456 });
85
85
 
86
86
  ## Web Control Panel
87
87
 
88
- When you run `lsji serve`, you get a real-time dashboard at **http://localhost:3456**:
88
+ When you run `npx @game_ryo/lsji serve`, you get a real-time dashboard at **http://localhost:3456**:
89
89
 
90
90
  | Panel | Features |
91
91
  |-------|----------|
@@ -117,7 +117,7 @@ export default {
117
117
  ```
118
118
 
119
119
  ```bash
120
- lsji plugin list # Shows: my-tools (1 tools)
120
+ npx @game_ryo/lsji plugin list # Shows: my-tools (1 tools)
121
121
  ```
122
122
 
123
123
  ## Architecture
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@game_ryo/lsji",
3
- "version": "1.1.0",
3
+ "version": "1.2.1",
4
4
  "description": "A general-purpose reinforcement learning agent framework (Node.js) with LLM agent capabilities",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -23,6 +23,7 @@
23
23
  "vitest": "^2.0.0"
24
24
  },
25
25
  "dependencies": {
26
+ "better-sqlite3": "^13.0.3",
26
27
  "cors": "^2.8.5",
27
28
  "express": "^4.19.2",
28
29
  "openai": "^7.8.0",
@@ -32,4 +33,4 @@
32
33
  "publishConfig": {
33
34
  "access": "public"
34
35
  }
35
- }
36
+ }
package/src/cli.js CHANGED
@@ -215,6 +215,7 @@ export async function main() {
215
215
 
216
216
  console.log(`Running agent on task: ${task}`);
217
217
 
218
+ const storageConfig = { type: storageType, options: { path: dbPath } };
218
219
  const agent = await createLLMAgent({
219
220
  llm: {
220
221
  provider: options.provider || 'openai',
@@ -234,6 +235,7 @@ export async function main() {
234
235
  semantic: options.semantic === 'true',
235
236
  episodic: options.episodic !== 'false',
236
237
  },
238
+ storage: storageConfig,
237
239
  });
238
240
 
239
241
  const result = await agent.run(task, {
@@ -257,6 +259,7 @@ export async function main() {
257
259
 
258
260
  console.log(`Running durable agent on task: ${task}`);
259
261
 
262
+ const storageConfig = { type: storageType, options: { path: dbPath } };
260
263
  const agent = await createLLMAgent({
261
264
  llm: {
262
265
  provider: options.provider || 'openai',
@@ -266,6 +269,7 @@ export async function main() {
266
269
  execution: {
267
270
  checkpointInterval: parseInt(options.checkpointInterval) || 3,
268
271
  },
272
+ storage: storageConfig,
269
273
  });
270
274
 
271
275
  const result = await agent.runDurable(task, {
@@ -280,13 +284,16 @@ export async function main() {
280
284
  }
281
285
 
282
286
  case 'status': {
283
- const agent = await createLLMAgent({
284
- llm: { provider: 'openai', model: 'gpt-4o-mini' },
287
+ const storageConfig = { type: storageType, options: { path: dbPath } };
288
+ // Create agent without initializing LLM for status check
289
+ const { LLMAgent } = await import('./llm/llm-agent.js');
290
+ const agent = new LLMAgent({
291
+ llm: { provider: 'local', model: 'test', baseUrl: 'http://localhost:11434/v1' },
292
+ storage: storageConfig,
285
293
  });
286
-
294
+ // Don't initialize - just get status
287
295
  const status = agent.getStatus();
288
296
  output(status, jsonOutput);
289
- await agent.shutdown();
290
297
  break;
291
298
  }
292
299
 
@@ -199,7 +199,9 @@ export class ApprovalGate {
199
199
  * Create approval gate from config
200
200
  */
201
201
  export async function createApprovalGate(config = {}) {
202
- const store = await createApprovalStore(config.store || {});
202
+ // Support both config.store and config.storage for storage configuration
203
+ const storageConfig = config.storage || config.store || { type: 'sqlite', options: {} };
204
+ const store = await createApprovalStore(storageConfig);
203
205
  const notifier = new Notifier(config.notifier || {});
204
206
 
205
207
  return new ApprovalGate({
@@ -323,9 +323,11 @@ export class IdempotencyStore {
323
323
  * Create idempotency store from config
324
324
  */
325
325
  export async function createIdempotencyStore(config = {}) {
326
+ // Support both config.storage and config.type/config.options for storage configuration
327
+ const storageConfig = config.storage || { type: config.type || 'sqlite', options: config.options || {} };
326
328
  const storage = await createStorage(
327
- config.type || 'sqlite',
328
- config.options || {}
329
+ storageConfig.type || 'sqlite',
330
+ storageConfig.options || {}
329
331
  );
330
332
  const store = new IdempotencyStore(storage, { ttl: config.ttl });
331
333
  await store.initialize();
package/src/index.js CHANGED
@@ -96,4 +96,4 @@ export {
96
96
  } from './llm/plugins/index.js';
97
97
 
98
98
  // Version
99
- export const VERSION = '0.3.0';
99
+ export const VERSION = '1.2.1';
@@ -14,7 +14,7 @@ import { createPromptManager } from './prompt-manager.js';
14
14
  import { createExecutionEngine } from '../execution/engine.js';
15
15
  import { createApprovalGate } from '../execution/hitl/approval-gate.js';
16
16
  import { createBudgetController } from '../execution/budget/index.js';
17
- import { IdempotencyStore } from '../execution/idempotency.js';
17
+ import { createIdempotencyStore } from '../execution/idempotency.js';
18
18
  import { v4 as uuidv4 } from 'uuid';
19
19
 
20
20
  /**
@@ -27,6 +27,8 @@ import { v4 as uuidv4 } from 'uuid';
27
27
  * @property {Object} [hitl] - HITL approval config
28
28
  * @property {Object} [budget] - Budget control config
29
29
  * @property {Object} [idempotency] - Idempotency config
30
+ * @property {Object} [prompts] - Prompt manager config
31
+ * @property {string} [sessionId] - Session ID for conversation memory
30
32
  */
31
33
 
32
34
  /**
@@ -61,6 +63,9 @@ export class LLMAgent {
61
63
  throw new Error(`LLM validation failed: ${valid.error}`);
62
64
  }
63
65
 
66
+ // Get storage config
67
+ const storageConfig = this.config.storage || { type: 'sqlite', options: {} };
68
+
64
69
  // Initialize tool registry
65
70
  this.tools = createToolRegistry({
66
71
  idempotencyStore: this.idempotency,
@@ -69,24 +74,39 @@ export class LLMAgent {
69
74
 
70
75
  // Initialize memory systems
71
76
  if (this.config.memory?.conversation !== false) {
72
- this.memory.conversation = await createConversationMemory(this.config.memory?.conversation);
77
+ this.memory.conversation = await createConversationMemory({
78
+ ...this.config.memory?.conversation,
79
+ storage: storageConfig
80
+ });
73
81
  await this.memory.conversation.startSession(this.config.sessionId);
74
82
  }
75
83
 
76
84
  if (this.config.memory?.semantic) {
77
- this.memory.semantic = await createSemanticMemory(this.config.memory.semantic);
85
+ this.memory.semantic = await createSemanticMemory({
86
+ ...this.config.memory.semantic,
87
+ storage: storageConfig
88
+ });
78
89
  }
79
90
 
80
91
  if (this.config.memory?.episodic) {
81
- this.memory.episodic = await createEpisodicMemory(this.config.memory.episodic);
92
+ this.memory.episodic = await createEpisodicMemory({
93
+ ...this.config.memory.episodic,
94
+ storage: storageConfig
95
+ });
82
96
  }
83
97
 
84
98
  // Initialize execution engine
85
- this.execution = await createExecutionEngine(this.config.execution);
99
+ this.execution = await createExecutionEngine({
100
+ ...this.config.execution,
101
+ storage: storageConfig
102
+ });
86
103
 
87
104
  // Initialize HITL
88
105
  if (this.config.hitl?.enabled !== false) {
89
- this.hitl = await createApprovalGate(this.config.hitl);
106
+ this.hitl = await createApprovalGate({
107
+ ...this.config.hitl,
108
+ store: storageConfig
109
+ });
90
110
  // Update tool registry with HITL
91
111
  this.tools.approvalGate = this.hitl;
92
112
  }
@@ -95,7 +115,10 @@ export class LLMAgent {
95
115
  this.budget = createBudgetController(this.config.budget);
96
116
 
97
117
  // Initialize idempotency
98
- this.idempotency = await IdempotencyStore.create(this.config.idempotency);
118
+ this.idempotency = await createIdempotencyStore({
119
+ ...this.config.idempotency,
120
+ ...storageConfig
121
+ });
99
122
  this.tools.idempotencyStore = this.idempotency;
100
123
 
101
124
  // Initialize prompt manager
@@ -37,8 +37,9 @@ export class ConversationMemory {
37
37
  async initialize() {
38
38
  if (this.initialized) return;
39
39
 
40
- if (this.storage.db) {
41
- await this.storage.db.exec(`
40
+ // Use the new storage interface method
41
+ if (typeof this.storage.exec === 'function') {
42
+ await this.storage.exec(`
42
43
  CREATE TABLE IF NOT EXISTS conversations (
43
44
  id TEXT PRIMARY KEY,
44
45
  session_id TEXT NOT NULL,
@@ -52,11 +53,11 @@ export class ConversationMemory {
52
53
  )
53
54
  `);
54
55
 
55
- await this.storage.db.exec(`
56
+ await this.storage.exec(`
56
57
  CREATE INDEX IF NOT EXISTS idx_conversations_session ON conversations(session_id)
57
58
  `);
58
59
 
59
- await this.storage.db.exec(`
60
+ await this.storage.exec(`
60
61
  CREATE INDEX IF NOT EXISTS idx_conversations_created ON conversations(created_at)
61
62
  `);
62
63
  }
@@ -86,8 +87,8 @@ export class ConversationMemory {
86
87
  async loadSession(sessionId) {
87
88
  await this.initialize();
88
89
 
89
- if (this.storage.db) {
90
- const rows = await this.storage.db.all(
90
+ if (typeof this.storage.all === 'function') {
91
+ const rows = await this.storage.all(
91
92
  'SELECT * FROM conversations WHERE session_id = ? ORDER BY created_at ASC',
92
93
  [sessionId]
93
94
  );
@@ -123,12 +124,12 @@ export class ConversationMemory {
123
124
  this.messages.push(msg);
124
125
 
125
126
  // Persist to storage
126
- if (this.storage.db && this.sessionId) {
127
+ if (typeof this.storage.run === 'function' && this.sessionId) {
127
128
  const tokens = this.tokenCounter
128
129
  ? await this.tokenCounter.estimateTokens([msg], { provider: 'openai', model: 'gpt-4o-mini' })
129
130
  : 0;
130
131
 
131
- await this.storage.db.run(
132
+ await this.storage.run(
132
133
  `INSERT INTO conversations (id, session_id, role, content, name, tool_call_id, tool_calls, tokens, created_at)
133
134
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
134
135
  [
@@ -229,8 +230,8 @@ export class ConversationMemory {
229
230
  async clear() {
230
231
  this.messages = [];
231
232
 
232
- if (this.storage.db && this.sessionId) {
233
- await this.storage.db.run(
233
+ if (typeof this.storage.run === 'function' && this.sessionId) {
234
+ await this.storage.run(
234
235
  'DELETE FROM conversations WHERE session_id = ?',
235
236
  [this.sessionId]
236
237
  );