kbs 0.0.1 → 0.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.
Files changed (86) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/deploy-github-pages.yml +52 -0
  3. data/CHANGELOG.md +68 -2
  4. data/README.md +235 -334
  5. data/docs/DOCUMENTATION_STATUS.md +158 -0
  6. data/docs/advanced/custom-persistence.md +775 -0
  7. data/docs/advanced/debugging.md +726 -0
  8. data/docs/advanced/index.md +8 -0
  9. data/docs/advanced/performance.md +832 -0
  10. data/docs/advanced/testing.md +691 -0
  11. data/docs/api/blackboard.md +1157 -0
  12. data/docs/api/engine.md +978 -0
  13. data/docs/api/facts.md +1212 -0
  14. data/docs/api/index.md +12 -0
  15. data/docs/api/rules.md +1034 -0
  16. data/docs/architecture/blackboard.md +553 -0
  17. data/docs/architecture/index.md +277 -0
  18. data/docs/architecture/network-structure.md +343 -0
  19. data/docs/architecture/rete-algorithm.md +737 -0
  20. data/docs/assets/css/custom.css +83 -0
  21. data/docs/assets/images/blackboard-architecture.svg +136 -0
  22. data/docs/assets/images/compiled-network.svg +101 -0
  23. data/docs/assets/images/fact-assertion-flow.svg +117 -0
  24. data/docs/assets/images/kbs.jpg +0 -0
  25. data/docs/assets/images/pattern-matching-trace.svg +136 -0
  26. data/docs/assets/images/rete-network-layers.svg +96 -0
  27. data/docs/assets/images/system-layers.svg +69 -0
  28. data/docs/assets/images/trading-signal-network.svg +139 -0
  29. data/docs/assets/js/mathjax.js +17 -0
  30. data/docs/examples/expert-systems.md +1031 -0
  31. data/docs/examples/index.md +9 -0
  32. data/docs/examples/multi-agent.md +1335 -0
  33. data/docs/examples/stock-trading.md +488 -0
  34. data/docs/guides/blackboard-memory.md +558 -0
  35. data/docs/guides/dsl.md +1321 -0
  36. data/docs/guides/facts.md +652 -0
  37. data/docs/guides/getting-started.md +383 -0
  38. data/docs/guides/index.md +23 -0
  39. data/docs/guides/negation.md +529 -0
  40. data/docs/guides/pattern-matching.md +561 -0
  41. data/docs/guides/persistence.md +451 -0
  42. data/docs/guides/variable-binding.md +491 -0
  43. data/docs/guides/writing-rules.md +755 -0
  44. data/docs/index.md +157 -0
  45. data/docs/installation.md +156 -0
  46. data/docs/quick-start.md +228 -0
  47. data/examples/README.md +2 -2
  48. data/examples/advanced_example.rb +2 -2
  49. data/examples/advanced_example_dsl.rb +224 -0
  50. data/examples/ai_enhanced_kbs.rb +1 -1
  51. data/examples/ai_enhanced_kbs_dsl.rb +538 -0
  52. data/examples/blackboard_demo_dsl.rb +50 -0
  53. data/examples/car_diagnostic.rb +1 -1
  54. data/examples/car_diagnostic_dsl.rb +54 -0
  55. data/examples/concurrent_inference_demo.rb +5 -5
  56. data/examples/concurrent_inference_demo_dsl.rb +363 -0
  57. data/examples/csv_trading_system.rb +1 -1
  58. data/examples/csv_trading_system_dsl.rb +525 -0
  59. data/examples/knowledge_base.db +0 -0
  60. data/examples/portfolio_rebalancing_system.rb +2 -2
  61. data/examples/portfolio_rebalancing_system_dsl.rb +613 -0
  62. data/examples/redis_trading_demo_dsl.rb +177 -0
  63. data/examples/run_all.rb +50 -0
  64. data/examples/run_all_dsl.rb +49 -0
  65. data/examples/stock_trading_advanced.rb +1 -1
  66. data/examples/stock_trading_advanced_dsl.rb +404 -0
  67. data/examples/temp.txt +7693 -0
  68. data/examples/temp_dsl.txt +8447 -0
  69. data/examples/timestamped_trading.rb +1 -1
  70. data/examples/timestamped_trading_dsl.rb +258 -0
  71. data/examples/trading_demo.rb +1 -1
  72. data/examples/trading_demo_dsl.rb +322 -0
  73. data/examples/working_demo.rb +1 -1
  74. data/examples/working_demo_dsl.rb +160 -0
  75. data/lib/kbs/blackboard/engine.rb +3 -3
  76. data/lib/kbs/blackboard/fact.rb +1 -1
  77. data/lib/kbs/condition.rb +1 -1
  78. data/lib/kbs/dsl/knowledge_base.rb +1 -1
  79. data/lib/kbs/dsl/variable.rb +1 -1
  80. data/lib/kbs/{rete_engine.rb → engine.rb} +1 -1
  81. data/lib/kbs/fact.rb +1 -1
  82. data/lib/kbs/version.rb +1 -1
  83. data/lib/kbs.rb +2 -2
  84. data/mkdocs.yml +181 -0
  85. metadata +66 -6
  86. data/examples/stock_trading_system.rb.bak +0 -563
@@ -0,0 +1,558 @@
1
+ # Blackboard Memory
2
+
3
+ The blackboard pattern enables multiple agents to collaborate through a shared persistent workspace. This guide covers using KBS's blackboard memory for multi-agent systems, audit trails, and persistent knowledge bases.
4
+
5
+ ## What is Blackboard Memory?
6
+
7
+ The blackboard architecture consists of three components:
8
+
9
+ 1. **Blackboard** (`KBS::Blackboard::Memory`) - Central shared workspace
10
+ 2. **Knowledge Sources** (Agents) - Independent specialists that read/write facts
11
+ 3. **Control** (Rules + Priority) - Determines which agent acts when
12
+
13
+ ![Blackboard Architecture](../assets/images/blackboard-architecture.svg)
14
+
15
+ *KBS uses the blackboard pattern for persistent, multi-agent reasoning with complete audit trails.*
16
+
17
+ ## Basic Usage
18
+
19
+ ### Creating a Blackboard Engine
20
+
21
+ ```ruby
22
+ require 'kbs'
23
+
24
+ # With SQLite (default)
25
+ engine = KBS::Blackboard::Engine.new(db_path: 'kb.db')
26
+
27
+ # Facts persist across restarts
28
+ engine.add_fact(:sensor, { id: "bedroom", temp: 28 })
29
+ engine.close
30
+
31
+ # Next run
32
+ engine = KBS::Blackboard::Engine.new(db_path: 'kb.db')
33
+ puts engine.facts.size # => 1
34
+ ```
35
+
36
+ ### Blackboard vs Regular Engine
37
+
38
+ ```ruby
39
+ # Regular engine (transient)
40
+ regular = KBS::Engine.new
41
+ regular.add_fact(:foo, { bar: 1 })
42
+ # Lost on exit
43
+
44
+ # Blackboard engine (persistent)
45
+ blackboard = KBS::Blackboard::Engine.new(db_path: 'kb.db')
46
+ blackboard.add_fact(:foo, { bar: 1 })
47
+ blackboard.close
48
+ # Persisted to database
49
+ ```
50
+
51
+ ## Persistent Facts
52
+
53
+ ### Fact Lifecycle
54
+
55
+ ```ruby
56
+ # Create fact
57
+ fact = engine.add_fact(:sensor, { id: "bedroom", temp: 28 })
58
+
59
+ # Fact has UUID
60
+ puts fact.id # => "550e8400-e29b-41d4-a716-446655440000"
61
+
62
+ # Update fact
63
+ engine.update_fact(fact.id, { temp: 30 })
64
+
65
+ # Query fact history
66
+ history = engine.fact_history(fact.id)
67
+ history.each do |entry|
68
+ puts "#{entry[:timestamp]}: #{entry[:operation]} - #{entry[:attributes]}"
69
+ end
70
+
71
+ # Delete fact
72
+ engine.delete_fact(fact.id)
73
+ ```
74
+
75
+ ### Fact Attributes
76
+
77
+ Blackboard facts support the same interface as regular facts:
78
+
79
+ ```ruby
80
+ fact = engine.add_fact(:stock, { symbol: "AAPL", price: 150 })
81
+
82
+ # Access
83
+ fact.type # => :stock
84
+ fact[:symbol] # => "AAPL"
85
+ fact.attributes # => { symbol: "AAPL", price: 150 }
86
+ fact.id # => UUID string
87
+
88
+ # Metadata
89
+ fact.created_at # => Time object
90
+ fact.updated_at # => Time object
91
+ ```
92
+
93
+ ## Message Queue
94
+
95
+ The blackboard includes a priority-based message queue for agent communication:
96
+
97
+ ### Sending Messages
98
+
99
+ ```ruby
100
+ # Add message to queue
101
+ engine.send_message(:alerts, "High temperature detected", priority: 10)
102
+ engine.send_message(:alerts, "Critical failure", priority: 100) # Higher priority
103
+ ```
104
+
105
+ ### Receiving Messages
106
+
107
+ ```ruby
108
+ # Pop highest priority message
109
+ msg = engine.pop_message(:alerts)
110
+ puts msg[:content] # => "Critical failure"
111
+ puts msg[:priority] # => 100
112
+
113
+ # Process all messages
114
+ while (msg = engine.pop_message(:alerts))
115
+ process_alert(msg[:content])
116
+ end
117
+ ```
118
+
119
+ ### Message Topics
120
+
121
+ Organize messages by topic:
122
+
123
+ ```ruby
124
+ # Different topics for different concerns
125
+ engine.send_message(:sensor_alerts, "Temp spike", priority: 50)
126
+ engine.send_message(:system_events, "Startup complete", priority: 10)
127
+ engine.send_message(:user_notifications, "Welcome!", priority: 1)
128
+
129
+ # Agents process their own topics
130
+ sensor_agent_msg = engine.pop_message(:sensor_alerts)
131
+ system_msg = engine.pop_message(:system_events)
132
+ user_msg = engine.pop_message(:user_notifications)
133
+ ```
134
+
135
+ ## Audit Trail
136
+
137
+ Blackboard automatically logs all changes:
138
+
139
+ ### Fact Audit Log
140
+
141
+ ```ruby
142
+ # Add fact
143
+ fact = engine.add_fact(:order, { id: 1, status: "pending" })
144
+
145
+ # Update fact
146
+ engine.update_fact(fact.id, { status: "processing" })
147
+
148
+ # Delete fact
149
+ engine.delete_fact(fact.id)
150
+
151
+ # Query audit trail
152
+ history = engine.fact_history(fact.id)
153
+ history.each do |entry|
154
+ puts "#{entry[:timestamp]}: #{entry[:operation]}"
155
+ puts " Attributes: #{entry[:attributes]}"
156
+ end
157
+
158
+ # Output:
159
+ # 2025-01-15 10:00:00: add
160
+ # Attributes: {id: 1, status: "pending"}
161
+ # 2025-01-15 10:01:00: update
162
+ # Attributes: {id: 1, status: "processing"}
163
+ # 2025-01-15 10:02:00: delete
164
+ # Attributes: {id: 1, status: "processing"}
165
+ ```
166
+
167
+ ### Rule Firing Log
168
+
169
+ ```ruby
170
+ # Enable rule firing audit
171
+ engine = KBS::Blackboard::Engine.new(
172
+ db_path: 'kb.db',
173
+ audit_rules: true
174
+ )
175
+
176
+ # Add and run rules
177
+ engine.add_rule(my_rule)
178
+ engine.run
179
+
180
+ # Query rule firings
181
+ firings = engine.rule_firings(rule_name: "my_rule")
182
+ firings.each do |firing|
183
+ puts "Rule '#{firing[:rule_name]}' fired at #{firing[:timestamp]}"
184
+ puts " Facts: #{firing[:fact_ids]}"
185
+ puts " Bindings: #{firing[:bindings]}"
186
+ end
187
+ ```
188
+
189
+ ## Multi-Agent Systems
190
+
191
+ ### Agent Pattern
192
+
193
+ ```ruby
194
+ class Agent
195
+ def initialize(name, engine)
196
+ @name = name
197
+ @engine = engine
198
+ end
199
+
200
+ def observe
201
+ # Read facts from blackboard
202
+ @engine.facts.select { |f| relevant?(f) }
203
+ end
204
+
205
+ def decide
206
+ # Apply agent's expertise
207
+ # Return action or nil
208
+ end
209
+
210
+ def act
211
+ # Write facts to blackboard
212
+ # Send messages to other agents
213
+ end
214
+
215
+ def run
216
+ observations = observe
217
+ action = decide(observations)
218
+ act(action) if action
219
+ end
220
+ end
221
+ ```
222
+
223
+ ### Example: Trading System
224
+
225
+ ```ruby
226
+ class MarketDataAgent < Agent
227
+ def run
228
+ # Fetch market data
229
+ data = fetch_market_data()
230
+
231
+ # Post to blackboard
232
+ @engine.add_fact(:market_data, {
233
+ symbol: data[:symbol],
234
+ price: data[:price],
235
+ volume: data[:volume],
236
+ timestamp: Time.now
237
+ })
238
+ end
239
+ end
240
+
241
+ class TradingAgent < Agent
242
+ def run
243
+ # Observe market data
244
+ market_facts = @engine.facts.select { |f| f.type == :market_data }
245
+
246
+ market_facts.each do |fact|
247
+ # Apply trading strategy
248
+ if buy_signal?(fact)
249
+ @engine.add_fact(:order, {
250
+ symbol: fact[:symbol],
251
+ type: "buy",
252
+ quantity: calculate_quantity(fact),
253
+ price: fact[:price]
254
+ })
255
+
256
+ @engine.send_message(:execution, "New buy order", priority: 50)
257
+ end
258
+ end
259
+ end
260
+
261
+ def buy_signal?(fact)
262
+ # Agent's expertise
263
+ fact[:price] < moving_average(fact[:symbol]) * 0.95
264
+ end
265
+ end
266
+
267
+ class ExecutionAgent < Agent
268
+ def run
269
+ # Check for execution messages
270
+ while (msg = @engine.pop_message(:execution))
271
+ # Find pending orders
272
+ orders = @engine.facts.select { |f|
273
+ f.type == :order && !f[:executed]
274
+ }
275
+
276
+ orders.each do |order|
277
+ execute_order(order)
278
+
279
+ # Update fact
280
+ @engine.update_fact(order.id, { executed: true })
281
+
282
+ # Notify
283
+ @engine.send_message(:notifications, "Order executed", priority: 10)
284
+ end
285
+ end
286
+ end
287
+ end
288
+
289
+ # Run agents in loop
290
+ engine = KBS::Blackboard::Engine.new(db_path: 'trading.db')
291
+
292
+ market_agent = MarketDataAgent.new("Market", engine)
293
+ trading_agent = TradingAgent.new("Trading", engine)
294
+ execution_agent = ExecutionAgent.new("Execution", engine)
295
+
296
+ loop do
297
+ market_agent.run # Fetch data → blackboard
298
+ trading_agent.run # Analyze → create orders
299
+ execution_agent.run # Execute orders
300
+ sleep 1
301
+ end
302
+ ```
303
+
304
+ ## Transactions
305
+
306
+ Blackboard supports ACID transactions (SQLite backend):
307
+
308
+ ```ruby
309
+ # Transaction succeeds
310
+ engine.transaction do
311
+ engine.add_fact(:account, { id: 1, balance: 1000 })
312
+ engine.add_fact(:account, { id: 2, balance: 500 })
313
+ end
314
+ # Both facts committed
315
+
316
+ # Transaction fails
317
+ begin
318
+ engine.transaction do
319
+ engine.add_fact(:account, { id: 3, balance: 100 })
320
+ raise "Error!"
321
+ engine.add_fact(:account, { id: 4, balance: 200 }) # Never reached
322
+ end
323
+ rescue => e
324
+ puts "Transaction rolled back"
325
+ end
326
+ # No facts committed
327
+ ```
328
+
329
+ ## Observers
330
+
331
+ Monitor blackboard changes in real-time:
332
+
333
+ ```ruby
334
+ class FactObserver
335
+ def update(operation, fact)
336
+ case operation
337
+ when :add
338
+ puts "Fact added: #{fact.type} - #{fact.attributes}"
339
+ when :remove
340
+ puts "Fact removed: #{fact.type} - #{fact.attributes}"
341
+ when :update
342
+ puts "Fact updated: #{fact.type} - #{fact.attributes}"
343
+ end
344
+ end
345
+ end
346
+
347
+ observer = FactObserver.new
348
+ engine.memory.add_observer(observer)
349
+
350
+ engine.add_fact(:sensor, { temp: 28 })
351
+ # Output: Fact added: sensor - {:temp=>28}
352
+ ```
353
+
354
+ ## Performance Tuning
355
+
356
+ ### SQLite Optimization
357
+
358
+ ```ruby
359
+ engine = KBS::Blackboard::Engine.new(
360
+ db_path: 'kb.db',
361
+ journal_mode: 'WAL', # Write-Ahead Logging
362
+ synchronous: 'NORMAL', # Balance durability/speed
363
+ cache_size: -64000, # 64MB cache
364
+ busy_timeout: 5000 # Wait 5s for locks
365
+ )
366
+ ```
367
+
368
+ ### Redis for Speed
369
+
370
+ ```ruby
371
+ require 'kbs/blackboard/persistence/redis_store'
372
+
373
+ store = KBS::Blackboard::Persistence::RedisStore.new(
374
+ url: 'redis://localhost:6379/0'
375
+ )
376
+
377
+ engine = KBS::Blackboard::Engine.new(store: store)
378
+
379
+ # 10-100x faster than SQLite
380
+ # Perfect for high-frequency updates
381
+ ```
382
+
383
+ ### Hybrid for Production
384
+
385
+ ```ruby
386
+ require 'kbs/blackboard/persistence/hybrid_store'
387
+
388
+ store = KBS::Blackboard::Persistence::HybridStore.new(
389
+ redis_url: 'redis://localhost:6379/0',
390
+ db_path: 'audit.db'
391
+ )
392
+
393
+ engine = KBS::Blackboard::Engine.new(store: store)
394
+
395
+ # Fast access (Redis) + durable audit (SQLite)
396
+ ```
397
+
398
+ ## Best Practices
399
+
400
+ ### 1. Use UUIDs for Fact References
401
+
402
+ ```ruby
403
+ # Good: Store fact UUID
404
+ order_id = engine.add_fact(:order, { ... }).id
405
+ engine.add_fact(:payment, { order_id: order_id })
406
+
407
+ # Bad: Use attribute as reference
408
+ engine.add_fact(:order, { id: 1 })
409
+ engine.add_fact(:payment, { order_id: 1 }) # Fragile
410
+ ```
411
+
412
+ ### 2. Namespace Facts by Agent
413
+
414
+ ```ruby
415
+ # Good: Clear ownership
416
+ engine.add_fact(:market_agent_data, { ... })
417
+ engine.add_fact(:trading_agent_signal, { ... })
418
+
419
+ # Bad: Generic names
420
+ engine.add_fact(:data, { ... })
421
+ engine.add_fact(:signal, { ... })
422
+ ```
423
+
424
+ ### 3. Use Messages for Coordination
425
+
426
+ ```ruby
427
+ # Good: Explicit coordination
428
+ engine.send_message(:execution_queue, "Process order #123", priority: 50)
429
+
430
+ # Bad: Polling facts
431
+ loop do
432
+ orders = engine.facts.select { |f| f.type == :pending_order }
433
+ # Inefficient
434
+ end
435
+ ```
436
+
437
+ ### 4. Clean Up Old Facts
438
+
439
+ ```ruby
440
+ # Remove stale data
441
+ KBS::Rule.new("cleanup_old_facts", priority: 1) do |r|
442
+ r.conditions = [
443
+ KBS::Condition.new(:market_data, {
444
+ timestamp: :time?
445
+ }, predicate: lambda { |f|
446
+ (Time.now - f[:timestamp]) > 3600 # 1 hour old
447
+ })
448
+ ]
449
+
450
+ r.action = lambda do |facts, bindings|
451
+ engine.remove_fact(facts[0])
452
+ end
453
+ end
454
+ ```
455
+
456
+ ### 5. Use Transactions for Multi-Fact Updates
457
+
458
+ ```ruby
459
+ # Good: Atomic updates
460
+ engine.transaction do
461
+ engine.update_fact(account1_id, { balance: new_balance1 })
462
+ engine.update_fact(account2_id, { balance: new_balance2 })
463
+ end
464
+
465
+ # Bad: Separate updates (not atomic)
466
+ engine.update_fact(account1_id, { balance: new_balance1 })
467
+ engine.update_fact(account2_id, { balance: new_balance2 })
468
+ ```
469
+
470
+ ## Common Patterns
471
+
472
+ ### Leader Election
473
+
474
+ ```ruby
475
+ # Agent attempts to become leader
476
+ KBS::Rule.new("become_leader") do |r|
477
+ r.conditions = [
478
+ KBS::Condition.new(:agent, { name: :name? }),
479
+ KBS::Condition.new(:leader, {}, negated: true)
480
+ ]
481
+
482
+ r.action = lambda do |facts, bindings|
483
+ engine.add_fact(:leader, { name: bindings[:name?] })
484
+ puts "#{bindings[:name?]} is now leader"
485
+ end
486
+ end
487
+ ```
488
+
489
+ ### Distributed Locking
490
+
491
+ ```ruby
492
+ # Acquire lock
493
+ def acquire_lock(resource_id)
494
+ engine.transaction do
495
+ lock = engine.facts.find { |f|
496
+ f.type == :lock && f[:resource_id] == resource_id
497
+ }
498
+
499
+ if lock.nil?
500
+ engine.add_fact(:lock, {
501
+ resource_id: resource_id,
502
+ owner: @agent_id,
503
+ acquired_at: Time.now
504
+ })
505
+ true
506
+ else
507
+ false
508
+ end
509
+ end
510
+ end
511
+
512
+ # Release lock
513
+ def release_lock(resource_id)
514
+ lock = engine.facts.find { |f|
515
+ f.type == :lock &&
516
+ f[:resource_id] == resource_id &&
517
+ f[:owner] == @agent_id
518
+ }
519
+
520
+ engine.remove_fact(lock) if lock
521
+ end
522
+ ```
523
+
524
+ ### Event Sourcing
525
+
526
+ ```ruby
527
+ # Store events as facts
528
+ engine.add_fact(:event, {
529
+ type: "order_created",
530
+ aggregate_id: "order-123",
531
+ data: { item: "Widget", quantity: 5 },
532
+ timestamp: Time.now
533
+ })
534
+
535
+ # Reconstruct state from events
536
+ def rebuild_order(order_id)
537
+ events = engine.facts
538
+ .select { |f| f.type == :event && f[:aggregate_id] == order_id }
539
+ .sort_by { |f| f[:timestamp] }
540
+
541
+ state = {}
542
+ events.each do |event|
543
+ apply_event(state, event)
544
+ end
545
+ state
546
+ end
547
+ ```
548
+
549
+ ## Next Steps
550
+
551
+ - **[Persistence](persistence.md)** - Storage backend options
552
+ - **[Architecture](../architecture/blackboard.md)** - Blackboard implementation details
553
+ - **[Multi-Agent Example](../examples/multi-agent.md)** - Complete multi-agent system
554
+ - **[API Reference](../api/blackboard.md)** - Complete blackboard API
555
+
556
+ ---
557
+
558
+ *The blackboard pattern enables emergent intelligence through agent collaboration. Each agent contributes expertise to solve problems no single agent could solve alone.*