@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
package/package.json CHANGED
@@ -1,13 +1,10 @@
1
1
  {
2
2
  "name": "@game_ryo/lsji",
3
- "version": "0.1.0",
4
- "description": "A general-purpose reinforcement learning agent framework (Node.js)",
3
+ "version": "0.3.0",
4
+ "description": "A general-purpose reinforcement learning agent framework (Node.js) with LLM agent capabilities",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
7
- "exports": {
8
- ".": "./src/index.js",
9
- "./storage": "./src/storage/index.js"
10
- },
7
+ "main": "./src/index.js",
11
8
  "bin": {
12
9
  "lsji": "./bin/lsji.js"
13
10
  },
@@ -18,9 +15,20 @@
18
15
  "test": "vitest run",
19
16
  "test:watch": "vitest",
20
17
  "build": "echo 'No build step required (ESM)'",
21
- "lint": "echo 'No linter configured'"
18
+ "build:ui": "cd src/server/ui && npm install && npm run build",
19
+ "lint": "echo 'No linter configured'",
20
+ "serve": "node src/server/index.js"
22
21
  },
23
22
  "devDependencies": {
24
23
  "vitest": "^2.0.0"
24
+ },
25
+ "dependencies": {
26
+ "uuid": "^14.0.2",
27
+ "express": "^4.19.2",
28
+ "socket.io": "^4.7.5",
29
+ "cors": "^2.8.5"
30
+ },
31
+ "publishConfig": {
32
+ "access": "public"
25
33
  }
26
34
  }
package/src/cli.js CHANGED
@@ -1,38 +1,46 @@
1
1
  /**
2
2
  * LSJI CLI - Command Line Interface
3
3
  *
4
- * Provides train/play/status/start/stop commands.
5
- * Migrated from the original Cloudflare Workers HTTP endpoints.
4
+ * Provides train/play/status/start/stop commands for RL,
5
+ * plus agent/budget/hitl/checkpoint commands for LLM agents.
6
6
  */
7
7
 
8
8
  import { Agent } from './core/agent.js';
9
9
  import { QLearning } from './core/qlearning.js';
10
10
  import { createStorage } from './storage/index.js';
11
11
  import { RockPaperScissorsEnv, TrainingPattern, getTrainingAction } from './envs/rps.js';
12
+ import {
13
+ createLLMAgent,
14
+ createBudgetController,
15
+ createApprovalGate,
16
+ createExecutionEngine,
17
+ createIdempotencyStore
18
+ } from './index.js';
12
19
 
13
20
  // Hand names for display
14
21
  const HAND_NAMES = ['Rock', 'Scissors', 'Paper'];
15
22
 
16
23
  /**
17
24
  * Parse command line arguments
18
- * @returns {Object} Parsed arguments
19
25
  */
20
26
  function parseArgs() {
21
27
  const args = process.argv.slice(2);
22
28
  const command = args[0];
23
- const options = {};
29
+ const options = { _: [] };
24
30
 
25
31
  for (let i = 1; i < args.length; i++) {
26
32
  const arg = args[i];
27
- if (arg.startsWith('--')) {
33
+ if (arg.startsWith("--")) {
28
34
  const key = arg.slice(2);
29
35
  const nextArg = args[i + 1];
30
- if (nextArg && !nextArg.startsWith('--')) {
36
+ if (nextArg && !nextArg.startsWith("--")) {
31
37
  options[key] = nextArg;
32
38
  i++;
33
39
  } else {
34
40
  options[key] = true;
35
41
  }
42
+ } else {
43
+ options._.push(arg);
36
44
  }
37
45
  }
38
46
 
@@ -41,8 +49,6 @@ function parseArgs() {
41
49
 
42
50
  /**
43
51
  * Format output as JSON or table
44
- * @param {Object} data - Data to output
45
- * @param {boolean} json - Whether to output JSON
46
52
  */
47
53
  function output(data, json = false) {
48
54
  if (json) {
@@ -78,29 +84,25 @@ export async function main() {
78
84
  }
79
85
  }
80
86
 
81
- // Create Q-learning engine
82
- const qlearning = new QLearning({
83
- alpha: parseFloat(options.alpha) || 0.1,
84
- gamma: parseFloat(options.gamma) || 0.9,
85
- epsilon: parseFloat(options.epsilon) || 0.1,
86
- storage
87
- });
88
-
89
- // Create environment (Rock-Paper-Scissors)
90
- const opponent = options.opponent || 'random';
91
- const env = new RockPaperScissorsEnv({ opponent });
92
-
93
- // Create agent
94
- const agent = new Agent({ qlearning, storage, env });
95
-
96
87
  try {
97
88
  switch (command) {
89
+ // ===== RL Commands (existing) =====
98
90
  case 'train': {
91
+ const qlearning = new QLearning({
92
+ alpha: parseFloat(options.alpha) || 0.1,
93
+ gamma: parseFloat(options.gamma) || 0.9,
94
+ epsilon: parseFloat(options.epsilon) || 0.1,
95
+ storage
96
+ });
97
+
98
+ const opponent = options.opponent || 'random';
99
+ const env = new RockPaperScissorsEnv({ opponent });
100
+ const agent = new Agent({ qlearning, storage, env });
101
+
99
102
  const episodes = parseInt(options.episodes) || 200;
100
103
  const pattern = parseInt(options.pattern) || 0;
101
104
  const batchSize = parseInt(options.batchSize) || 200;
102
105
 
103
- // Create action selector based on pattern
104
106
  let actionSelector = null;
105
107
  if (pattern > 0) {
106
108
  actionSelector = (episode, lastAction) => getTrainingAction(pattern, episode, lastAction);
@@ -113,28 +115,31 @@ export async function main() {
113
115
  }
114
116
 
115
117
  case 'play': {
118
+ const qlearning = new QLearning({
119
+ alpha: parseFloat(options.alpha) || 0.1,
120
+ gamma: parseFloat(options.gamma) || 0.9,
121
+ epsilon: parseFloat(options.epsilon) || 0.1,
122
+ storage
123
+ });
124
+
125
+ const opponent = options.opponent || 'random';
126
+ const env = new RockPaperScissorsEnv({ opponent });
127
+ const agent = new Agent({ qlearning, storage, env });
128
+
116
129
  const hand = parseInt(options.hand);
117
130
  if (isNaN(hand) || hand < 0 || hand > 2) {
118
131
  console.error('Error: --hand must be 0 (Rock), 1 (Scissors), or 2 (Paper)');
119
132
  process.exit(1);
120
133
  }
121
134
 
122
- // For play, we need to pass the user's hand to the environment
123
- // The RPS env uses its own opponent strategy, so we'll use a custom approach
124
135
  const state = await env.getState() || '0';
125
136
  const actionSize = env.actionSize();
126
137
  const aiHand = await qlearning.act(state, actionSize);
127
138
 
128
- // Calculate outcome manually for display
129
139
  const { judge, reward, outcome } = RockPaperScissorsEnv.calculateOutcome(aiHand, hand);
130
-
131
- // Step the environment with AI's action (to update state and Q-table)
132
140
  const result = await env.step(aiHand);
133
-
134
- // Update Q-table with actual result
135
141
  await qlearning.learnSimple(state, aiHand, result.reward);
136
142
 
137
- // Record battle
138
143
  await storage.addBattle({
139
144
  mode: 'test',
140
145
  handA: aiHand,
@@ -160,66 +165,382 @@ export async function main() {
160
165
  }
161
166
 
162
167
  case 'status': {
168
+ const qlearning = new QLearning({
169
+ alpha: parseFloat(options.alpha) || 0.1,
170
+ gamma: parseFloat(options.gamma) || 0.9,
171
+ epsilon: parseFloat(options.epsilon) || 0.1,
172
+ storage
173
+ });
174
+
175
+ const opponent = options.opponent || 'random';
176
+ const env = new RockPaperScissorsEnv({ opponent });
177
+ const agent = new Agent({ qlearning, storage, env });
178
+
163
179
  const result = await agent.status();
164
180
  output(result, jsonOutput);
165
181
  break;
166
182
  }
167
183
 
168
184
  case 'start': {
185
+ const qlearning = new QLearning({ storage });
186
+ const agent = new Agent({ qlearning, storage });
169
187
  const result = await agent.start();
170
188
  output(result, jsonOutput);
171
189
  break;
172
190
  }
173
191
 
174
192
  case 'stop': {
193
+ const qlearning = new QLearning({ storage });
194
+ const agent = new Agent({ qlearning, storage });
175
195
  const result = await agent.stop();
176
196
  output(result, jsonOutput);
177
197
  break;
178
198
  }
179
199
 
200
+ // ===== LLM Agent Commands (NEW) =====
201
+ case 'agent': {
202
+ const subcommand = options._[0] || 'help';
203
+
204
+ switch (subcommand) {
205
+ case 'run': {
206
+ const task = options.task || options._[1];
207
+ if (!task) {
208
+ console.error('Error: Task required. Use --task "your task here"');
209
+ process.exit(1);
210
+ }
211
+
212
+ console.log(`Running agent on task: ${task}`);
213
+
214
+ const agent = await createLLMAgent({
215
+ llm: {
216
+ provider: options.provider || 'openai',
217
+ model: options.model || 'gpt-4o-mini',
218
+ apiKey: options.apiKey || process.env.OPENAI_API_KEY,
219
+ },
220
+ budget: {
221
+ maxCostPerRun: parseFloat(options.maxCost) || 10,
222
+ maxTokensPerRun: parseInt(options.maxTokens) || 100000,
223
+ },
224
+ hitl: {
225
+ enabled: options.hitl !== 'false',
226
+ defaultTimeout: parseInt(options.hitlTimeout) || 300000,
227
+ },
228
+ memory: {
229
+ conversation: options.conversation !== 'false',
230
+ semantic: options.semantic === 'true',
231
+ episodic: options.episodic !== 'false',
232
+ },
233
+ });
234
+
235
+ const result = await agent.run(task, {
236
+ runId: options.runId,
237
+ budgetId: options.budgetId,
238
+ hitlRequired: options.hitlRequired ? options.hitlRequired.split(',') : ['file_write', 'api_call', 'send_email', 'code_exec'],
239
+ maxSteps: parseInt(options.maxSteps) || 50,
240
+ });
241
+
242
+ console.log('Agent run complete:', result);
243
+ await agent.shutdown();
244
+ break;
245
+ }
246
+
247
+ case 'run-durable': {
248
+ const task = options.task || options._[1];
249
+ if (!task) {
250
+ console.error('Error: Task required. Use --task "your task here"');
251
+ process.exit(1);
252
+ }
253
+
254
+ console.log(`Running durable agent on task: ${task}`);
255
+
256
+ const agent = await createLLMAgent({
257
+ llm: {
258
+ provider: options.provider || 'openai',
259
+ model: options.model || 'gpt-4o-mini',
260
+ apiKey: options.apiKey || process.env.OPENAI_API_KEY,
261
+ },
262
+ execution: {
263
+ checkpointInterval: parseInt(options.checkpointInterval) || 3,
264
+ },
265
+ });
266
+
267
+ const result = await agent.runDurable(task, {
268
+ workflowId: options.workflowId,
269
+ checkpointEvery: parseInt(options.checkpointEvery) || 3,
270
+ resumeFrom: options.resumeFrom,
271
+ });
272
+
273
+ console.log('Durable agent run complete:', result);
274
+ await agent.shutdown();
275
+ break;
276
+ }
277
+
278
+ case 'status': {
279
+ const agent = await createLLMAgent({
280
+ llm: { provider: 'openai', model: 'gpt-4o-mini' },
281
+ });
282
+
283
+ const status = agent.getStatus();
284
+ output(status, jsonOutput);
285
+ await agent.shutdown();
286
+ break;
287
+ }
288
+
289
+ default:
290
+ console.log(`
291
+ Agent Commands:
292
+ agent run --task "task description" Run agent on a task
293
+ agent run-durable --task "task" Run with checkpointing
294
+ agent status Show agent status
295
+ `);
296
+ }
297
+ break;
298
+ }
299
+
300
+ case 'budget': {
301
+ const subcommand = options._[0] || 'status';
302
+ const budget = createBudgetController({
303
+ maxCostPerRun: parseFloat(options.maxCost) || 10,
304
+ maxCostPerDay: parseFloat(options.maxDaily) || 50,
305
+ maxCostPerMonth: parseFloat(options.maxMonthly) || 500,
306
+ });
307
+
308
+ switch (subcommand) {
309
+ case 'status': {
310
+ const budgetId = options.budgetId || 'default';
311
+ const status = budget.getStatus(budgetId);
312
+ output(status, jsonOutput);
313
+ break;
314
+ }
315
+
316
+ case 'reset': {
317
+ const budgetId = options.budgetId || 'default';
318
+ budget.resetRunBudget(budgetId);
319
+ console.log(`Budget ${budgetId} reset`);
320
+ break;
321
+ }
322
+
323
+ case 'check': {
324
+ const budgetId = options.budgetId || 'default';
325
+ const estimatedCost = parseFloat(options.cost) || 0;
326
+ const estimatedTokens = parseInt(options.tokens) || 0;
327
+ const result = budget.checkBudget(budgetId, estimatedCost, estimatedTokens);
328
+ output(result, jsonOutput);
329
+ break;
330
+ }
331
+ }
332
+ break;
333
+ }
334
+
335
+ case 'hitl': {
336
+ const subcommand = options._[0] || 'help';
337
+
338
+ switch (subcommand) {
339
+ case 'approve': {
340
+ const approvalId = options.id || options._[1];
341
+ if (!approvalId) {
342
+ console.error('Error: Approval ID required');
343
+ process.exit(1);
344
+ }
345
+
346
+ const hitl = await createApprovalGate({});
347
+ const result = await hitl.approve(approvalId, {
348
+ decider: options.decider || 'cli-user',
349
+ reason: options.reason || 'Approved via CLI',
350
+ });
351
+
352
+ console.log('Approved:', result);
353
+ break;
354
+ }
355
+
356
+ case 'reject': {
357
+ const approvalId = options.id || options._[1];
358
+ if (!approvalId) {
359
+ console.error('Error: Approval ID required');
360
+ process.exit(1);
361
+ }
362
+
363
+ const hitl = await createApprovalGate({});
364
+ const result = await hitl.reject(approvalId, {
365
+ decider: options.decider || 'cli-user',
366
+ reason: options.reason || 'Rejected via CLI',
367
+ });
368
+
369
+ console.log('Rejected:', result);
370
+ break;
371
+ }
372
+
373
+ case 'list': {
374
+ const hitl = await createApprovalGate({});
375
+ const approvals = await hitl.getPendingApprovals(parseInt(options.limit) || 20);
376
+ output(approvals, jsonOutput);
377
+ break;
378
+ }
379
+
380
+ case 'status': {
381
+ const approvalId = options.id || options._[1];
382
+ if (!approvalId) {
383
+ console.error('Error: Approval ID required');
384
+ process.exit(1);
385
+ }
386
+
387
+ const hitl = await createApprovalGate({});
388
+ const approval = await hitl.getApproval(approvalId);
389
+ output(approval, jsonOutput);
390
+ break;
391
+ }
392
+ }
393
+ break;
394
+ }
395
+
396
+ case 'checkpoint': {
397
+ const subcommand = options._[0] || 'help';
398
+
399
+ switch (subcommand) {
400
+ case 'list': {
401
+ const engine = await createExecutionEngine({});
402
+ const workflows = await engine.listWorkflows();
403
+ output(workflows, jsonOutput);
404
+ break;
405
+ }
406
+
407
+ case 'show': {
408
+ const workflowId = options.workflowId || options._[1];
409
+ if (!workflowId) {
410
+ console.error('Error: Workflow ID required');
411
+ process.exit(1);
412
+ }
413
+
414
+ const engine = await createExecutionEngine({});
415
+ const checkpoints = await engine.getCheckpoints(workflowId);
416
+ output(checkpoints, jsonOutput);
417
+ break;
418
+ }
419
+
420
+ case 'recover': {
421
+ const workflowId = options.workflowId || options._[1];
422
+ if (!workflowId) {
423
+ console.error('Error: Workflow ID required');
424
+ process.exit(1);
425
+ }
426
+
427
+ const engine = await createExecutionEngine({});
428
+ const recovery = await engine.recover(workflowId);
429
+ output(recovery, jsonOutput);
430
+ break;
431
+ }
432
+ }
433
+ break;
434
+ }
435
+
436
+ case 'idempotency': {
437
+ const subcommand = options._[0] || 'help';
438
+
439
+ switch (subcommand) {
440
+ case 'cleanup': {
441
+ const store = await createIdempotencyStore({});
442
+ await store.cleanup();
443
+ console.log('Expired idempotency keys cleaned up');
444
+ break;
445
+ }
446
+
447
+ case 'list': {
448
+ const store = await createIdempotencyStore({});
449
+ const operation = options.operation || options._[1];
450
+ const keys = await store.getByOperation(operation, parseInt(options.limit) || 50);
451
+ output(keys, jsonOutput);
452
+ break;
453
+ }
454
+ }
455
+ break;
456
+ }
457
+
180
458
  case 'help':
181
459
  case '--help':
182
460
  case '-h':
183
461
  default: {
184
462
  console.log(`
185
- LSJI CLI - Reinforcement Learning Agent Framework
463
+ LSJI CLI - Reinforcement Learning & LLM Agent Framework
186
464
 
187
465
  Usage: lsji <command> [options]
188
466
 
189
- Commands:
190
- train Train the agent
191
- play Play against the agent
192
- status Show system status and statistics
193
- start Enable training/play
194
- stop Disable training/play
195
- help Show this help
196
-
197
- Options:
198
- --storage <type> Storage backend: sqlite, better-sqlite, memory (default: sqlite)
199
- --db-path <path> Database file path (default: ./lsji.db)
200
- --alpha <number> Learning rate (default: 0.1)
201
- --gamma <number> Discount factor (default: 0.9)
202
- --epsilon <number> Exploration rate (default: 0.1)
203
- --opponent <type> Opponent strategy: random, always_rock, counter, sequential (default: random)
204
- --json Output as JSON
205
-
206
- Train options:
207
- --episodes <number> Number of episodes (default: 200)
208
- --pattern <number> Training pattern 0-3 (default: 0)
209
- 0=random, 1=always_rock, 2=counter, 3=sequential
210
- --batch-size <number> Batch size for DB (default: 200)
211
-
212
- Play options:
213
- --hand <number> Your hand: 0=Rock, 1=Scissors, 2=Paper
467
+ === RL Commands ===
468
+ train Train the RL agent
469
+ play Play against the RL agent
470
+ status Show RL system status
471
+ start Enable RL training/play
472
+ stop Disable RL training/play
473
+
474
+ === LLM Agent Commands ===
475
+ agent run --task "task" Run LLM agent on task
476
+ agent run-durable --task "task" Run with checkpointing
477
+ agent status Show agent status
478
+
479
+ budget status [--budgetId] Show budget status
480
+ budget check --cost --tokens Check if operation allowed
481
+ budget reset [--budgetId] Reset run budget
482
+
483
+ hitl approve --id <id> Approve pending request
484
+ hitl reject --id <id> Reject pending request
485
+ hitl list [--limit] List pending approvals
486
+ hitl status --id <id> Show approval status
487
+
488
+ checkpoint list List workflows with checkpoints
489
+ checkpoint show --workflowId <id> Show checkpoints for workflow
490
+ checkpoint recover --workflowId <id> Recover from latest checkpoint
491
+
492
+ idempotency cleanup Clean expired keys
493
+ idempotency list --operation <op> List keys for operation
494
+
495
+ === Common Options ===
496
+ --storage <type> Storage: sqlite, better-sqlite, memory (default: sqlite)
497
+ --db-path <path> Database path (default: ./lsji.db)
498
+ --json Output as JSON
499
+
500
+ === RL Options ===
501
+ --alpha <num> Learning rate (default: 0.1)
502
+ --gamma <num> Discount factor (default: 0.9)
503
+ --epsilon <num> Exploration rate (default: 0.1)
504
+ --opponent <type> Opponent: random, always_rock, counter, sequential
505
+ --episodes <num> Training episodes (default: 200)
506
+ --pattern <num> Training pattern 0-3 (default: 0)
507
+ --hand <num> Your hand: 0=Rock, 1=Scissors, 2=Paper
508
+
509
+ === LLM Agent Options ===
510
+ --provider <name> LLM provider: openai, anthropic, local (default: openai)
511
+ --model <name> Model name (default: gpt-4o-mini)
512
+ --api-key <key> API key (or use OPENAI_API_KEY env)
513
+ --max-cost <num> Max cost per run USD (default: 10)
514
+ --max-tokens <num> Max tokens per run (default: 100000)
515
+ --hitl <true|false> Enable HITL (default: true)
516
+ --hitl-timeout <ms> Approval timeout (default: 300000)
517
+ --conversation <tf> Enable conversation memory (default: true)
518
+ --semantic <tf> Enable semantic memory (default: false)
519
+ --episodic <tf> Enable episodic memory (default: true)
520
+ --hitl-required <list> Comma-separated tools requiring approval
521
+ --max-steps <num> Max agent steps (default: 50)
522
+ --checkpoint-interval Checkpoint every N steps (default: 3)
523
+ --workflow-id <id> Workflow ID for durable runs
524
+ --resume-from <id> Resume from checkpoint ID
525
+
526
+ === Budget Options ===
527
+ --budget-id <id> Budget identifier
528
+ --max-daily <num> Max daily cost (default: 50)
529
+ --max-monthly <num> Max monthly cost (default: 500)
214
530
 
215
531
  Examples:
532
+ # RL Training
216
533
  lsji train --episodes 500
217
- lsji train --episodes 100 --pattern 1 --opponent always_rock
218
534
  lsji play --hand 0
219
535
  lsji status --json
220
- lsji start
221
- lsji stop
222
- `);
536
+
537
+ # LLM Agent
538
+ lsji agent run --task "Search for latest AI news and summarize"
539
+ lsji agent run-durable --task "Analyze codebase and create report" --workflow-id my-analysis
540
+ lsji budget status --budgetId project-1
541
+ lsji hitl list
542
+ lsji checkpoint show --workflowId my-analysis
543
+ `);
223
544
  break;
224
545
  }
225
546
  }
@@ -230,3 +551,15 @@ Examples:
230
551
  await storage.close();
231
552
  }
232
553
  }
554
+
555
+ // Auto-run main if this file is executed directly
556
+ import { fileURLToPath } from 'url';
557
+ import { dirname, resolve } from 'path';
558
+
559
+ const __filename = fileURLToPath(import.meta.url);
560
+ const __dirname = dirname(__filename);
561
+
562
+ if (process.argv[1] && resolve(process.argv[1]) === resolve(__filename)) {
563
+ main().catch(console.error);
564
+ }
565
+