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,1157 @@
1
+ # Blackboard API Reference
2
+
3
+ Complete API reference for blackboard memory classes.
4
+
5
+ ## Table of Contents
6
+
7
+ - [KBS::Blackboard::Memory](#kbsblackboardmemory) - Central blackboard workspace
8
+ - [KBS::Blackboard::MessageQueue](#kbsblackboardmessagequeue) - Inter-agent communication
9
+ - [KBS::Blackboard::AuditLog](#kbsblackboardauditlog) - Historical tracking
10
+ - [Usage Patterns](#usage-patterns)
11
+
12
+ ---
13
+
14
+ ## KBS::Blackboard::Memory
15
+
16
+ The central blackboard workspace that coordinates facts, messages, and audit logging.
17
+
18
+ **Architecture**: Composes three components:
19
+ 1. **Store** - Persistence layer (SQLite, Redis, or Hybrid)
20
+ 2. **MessageQueue** - Priority-based inter-agent messaging
21
+ 3. **AuditLog** - Complete history of fact changes and rule firings
22
+
23
+ ---
24
+
25
+ ### Constructor
26
+
27
+ #### `initialize(db_path: ':memory:', store: nil)`
28
+
29
+ Creates a new blackboard memory.
30
+
31
+ **Parameters**:
32
+ - `db_path` (String, optional) - Path to SQLite database (default: `:memory:`)
33
+ - `store` (KBS::Blackboard::Persistence::Store, optional) - Custom store (default: `nil`, creates SQLiteStore)
34
+
35
+ **Returns**: `KBS::Blackboard::Memory` instance
36
+
37
+ **Side Effects**:
38
+ - Generates session UUID
39
+ - Creates or connects to persistence store
40
+ - Initializes message queue and audit log
41
+ - Sets up database tables/indexes
42
+
43
+ **Example - In-Memory**:
44
+ ```ruby
45
+ memory = KBS::Blackboard::Memory.new
46
+ # Blackboard stored in RAM (lost on exit)
47
+ ```
48
+
49
+ **Example - SQLite Persistence**:
50
+ ```ruby
51
+ memory = KBS::Blackboard::Memory.new(db_path: 'knowledge_base.db')
52
+ # Facts persisted to knowledge_base.db
53
+ ```
54
+
55
+ **Example - Redis Store**:
56
+ ```ruby
57
+ require 'kbs/blackboard/persistence/redis_store'
58
+
59
+ store = KBS::Blackboard::Persistence::RedisStore.new(url: 'redis://localhost:6379/0')
60
+ memory = KBS::Blackboard::Memory.new(store: store)
61
+ # Fast, distributed persistence
62
+ ```
63
+
64
+ **Example - Hybrid Store**:
65
+ ```ruby
66
+ require 'kbs/blackboard/persistence/hybrid_store'
67
+
68
+ store = KBS::Blackboard::Persistence::HybridStore.new(
69
+ redis_url: 'redis://localhost:6379/0',
70
+ db_path: 'audit.db'
71
+ )
72
+ memory = KBS::Blackboard::Memory.new(store: store)
73
+ # Facts in Redis, audit trail in SQLite
74
+ ```
75
+
76
+ ---
77
+
78
+ ### Public Attributes
79
+
80
+ #### `session_id`
81
+
82
+ **Type**: `String`
83
+
84
+ **Read-only**: Yes (via `attr_reader`)
85
+
86
+ **Description**: Unique session identifier (UUID)
87
+
88
+ **Example**:
89
+ ```ruby
90
+ memory = KBS::Blackboard::Memory.new
91
+ puts memory.session_id # => "550e8400-e29b-41d4-a716-446655440000"
92
+ ```
93
+
94
+ **Use Cases**:
95
+ - Filter audit log by session
96
+ - Separate facts from different runs
97
+ - Debugging multi-session scenarios
98
+
99
+ ---
100
+
101
+ #### `store`
102
+
103
+ **Type**: `KBS::Blackboard::Persistence::Store`
104
+
105
+ **Read-only**: Yes (via `attr_reader`)
106
+
107
+ **Description**: The underlying persistence store
108
+
109
+ **Example**:
110
+ ```ruby
111
+ memory = KBS::Blackboard::Memory.new(db_path: 'kb.db')
112
+ puts memory.store.class # => KBS::Blackboard::Persistence::SqliteStore
113
+ ```
114
+
115
+ ---
116
+
117
+ #### `message_queue`
118
+
119
+ **Type**: `KBS::Blackboard::MessageQueue`
120
+
121
+ **Read-only**: Yes (via `attr_reader`)
122
+
123
+ **Description**: The message queue for inter-agent communication
124
+
125
+ **Example**:
126
+ ```ruby
127
+ memory.message_queue.post("agent1", "alerts", { level: "critical" })
128
+ ```
129
+
130
+ ---
131
+
132
+ #### `audit_log`
133
+
134
+ **Type**: `KBS::Blackboard::AuditLog`
135
+
136
+ **Read-only**: Yes (via `attr_reader`)
137
+
138
+ **Description**: The audit log for tracking all changes
139
+
140
+ **Example**:
141
+ ```ruby
142
+ history = memory.audit_log.fact_history(fact.uuid)
143
+ ```
144
+
145
+ ---
146
+
147
+ ### Fact Management Methods
148
+
149
+ #### `add_fact(type, attributes = {})`
150
+
151
+ Adds a persistent fact to the blackboard.
152
+
153
+ **Parameters**:
154
+ - `type` (Symbol) - Fact type
155
+ - `attributes` (Hash, optional) - Fact attributes (default: `{}`)
156
+
157
+ **Returns**: `KBS::Blackboard::Fact` - Persistent fact with UUID
158
+
159
+ **Side Effects**:
160
+ - Generates UUID for fact
161
+ - Saves fact to store (within transaction)
162
+ - Logs addition to audit log
163
+ - Notifies observers
164
+
165
+ **Example**:
166
+ ```ruby
167
+ fact = memory.add_fact(:temperature, location: "server_room", value: 85)
168
+ puts fact.uuid # => "550e8400-e29b-41d4-a716-446655440000"
169
+ puts fact.type # => :temperature
170
+ puts fact[:value] # => 85
171
+ ```
172
+
173
+ **Transaction Handling**:
174
+ ```ruby
175
+ memory.transaction do
176
+ fact1 = memory.add_fact(:order, id: 1, status: "pending")
177
+ fact2 = memory.add_fact(:inventory, item: "ABC", quantity: 100)
178
+ # Both facts committed together
179
+ end
180
+ ```
181
+
182
+ ---
183
+
184
+ #### `remove_fact(fact)`
185
+
186
+ Removes a fact from the blackboard.
187
+
188
+ **Parameters**:
189
+ - `fact` (KBS::Blackboard::Fact or String) - Fact object or UUID
190
+
191
+ **Returns**: `nil`
192
+
193
+ **Side Effects**:
194
+ - Marks fact as inactive in store
195
+ - Logs removal to audit log
196
+ - Notifies observers
197
+
198
+ **Example**:
199
+ ```ruby
200
+ fact = memory.add_fact(:temperature, value: 85)
201
+ memory.remove_fact(fact)
202
+
203
+ # Or by UUID
204
+ memory.remove_fact("550e8400-e29b-41d4-a716-446655440000")
205
+
206
+ # Fact remains in audit log
207
+ history = memory.get_history(fact.uuid)
208
+ puts history.last[:action] # => "REMOVE"
209
+ ```
210
+
211
+ ---
212
+
213
+ #### `update_fact(fact, new_attributes)`
214
+
215
+ Updates a fact's attributes.
216
+
217
+ **Parameters**:
218
+ - `fact` (KBS::Blackboard::Fact or String) - Fact object or UUID
219
+ - `new_attributes` (Hash) - New attributes to merge
220
+
221
+ **Returns**: `nil`
222
+
223
+ **Side Effects**:
224
+ - Updates fact in store
225
+ - Logs update to audit log
226
+
227
+ **Example**:
228
+ ```ruby
229
+ fact = memory.add_fact(:temperature, location: "server_room", value: 85)
230
+ memory.update_fact(fact, value: 90, timestamp: Time.now)
231
+
232
+ # Or by UUID
233
+ memory.update_fact(fact.uuid, value: 95)
234
+ ```
235
+
236
+ **Note**: Updates do NOT notify observers or trigger rule re-evaluation. For that, retract and re-add the fact.
237
+
238
+ ---
239
+
240
+ #### `get_facts(type = nil, pattern = {})`
241
+
242
+ Retrieves facts from the blackboard.
243
+
244
+ **Parameters**:
245
+ - `type` (Symbol, optional) - Filter by fact type (default: `nil`, all types)
246
+ - `pattern` (Hash, optional) - Additional attribute filters (default: `{}`)
247
+
248
+ **Returns**: `Array<KBS::Blackboard::Fact>`
249
+
250
+ **Example**:
251
+ ```ruby
252
+ # Get all facts
253
+ all_facts = memory.get_facts
254
+
255
+ # Get all temperature facts
256
+ temps = memory.get_facts(:temperature)
257
+
258
+ # Get temperature facts from specific location
259
+ server_temps = memory.get_facts(:temperature, location: "server_room")
260
+ ```
261
+
262
+ **Performance**: O(N) where N = total facts (uses linear scan). For large datasets, consider `query_facts`.
263
+
264
+ ---
265
+
266
+ #### `facts`
267
+
268
+ Alias for `get_facts()`. Returns all facts.
269
+
270
+ **Returns**: `Array<KBS::Blackboard::Fact>`
271
+
272
+ **Example**:
273
+ ```ruby
274
+ puts "Total facts: #{memory.facts.size}"
275
+ ```
276
+
277
+ ---
278
+
279
+ #### `query_facts(sql_conditions = nil, params = [])`
280
+
281
+ Advanced SQL query for facts (SQLite store only).
282
+
283
+ **Parameters**:
284
+ - `sql_conditions` (String, optional) - SQL WHERE clause (default: `nil`)
285
+ - `params` (Array, optional) - Parameters for SQL query (default: `[]`)
286
+
287
+ **Returns**: `Array<KBS::Blackboard::Fact>`
288
+
289
+ **Example**:
290
+ ```ruby
291
+ # Query with SQL condition
292
+ high_temps = memory.query_facts(
293
+ "fact_type = ? AND json_extract(attributes, '$.value') > ?",
294
+ [:temperature, 80]
295
+ )
296
+
297
+ # Complex query
298
+ recent_errors = memory.query_facts(
299
+ "fact_type = ? AND datetime(json_extract(attributes, '$.timestamp')) > datetime(?)",
300
+ [:error, (Time.now - 3600).iso8601]
301
+ )
302
+ ```
303
+
304
+ **Important**: Only works with SQLite stores. Redis stores will raise NotImplementedError.
305
+
306
+ ---
307
+
308
+ ### Message Queue Methods
309
+
310
+ #### `post_message(sender, topic, content, priority: 0)`
311
+
312
+ Posts a message to the blackboard message queue.
313
+
314
+ **Parameters**:
315
+ - `sender` (String) - Sender identifier (e.g., agent name)
316
+ - `topic` (String) - Message topic (channel/category)
317
+ - `content` (Hash) - Message payload
318
+ - `priority` (Integer, optional) - Message priority (default: 0, higher = more urgent)
319
+
320
+ **Returns**: `nil`
321
+
322
+ **Side Effects**:
323
+ - Adds message to queue
324
+ - Persists to store
325
+
326
+ **Example**:
327
+ ```ruby
328
+ # Post high-priority alert
329
+ memory.post_message(
330
+ "temperature_agent",
331
+ "alerts",
332
+ { level: "critical", value: 110, location: "server_room" },
333
+ priority: 100
334
+ )
335
+
336
+ # Post normal-priority task
337
+ memory.post_message(
338
+ "scheduler",
339
+ "tasks",
340
+ { task_name: "cleanup", params: {} },
341
+ priority: 10
342
+ )
343
+ ```
344
+
345
+ **Message Ordering**: Messages consumed in priority order (highest first), then FIFO within same priority.
346
+
347
+ ---
348
+
349
+ #### `consume_message(topic, consumer)`
350
+
351
+ Retrieves and removes the highest priority message from a topic.
352
+
353
+ **Parameters**:
354
+ - `topic` (String) - Topic to consume from
355
+ - `consumer` (String) - Consumer identifier (for audit trail)
356
+
357
+ **Returns**: `Hash` or `nil` - Message hash with `:id`, `:sender`, `:topic`, `:content`, `:priority`, `:posted_at`, or `nil` if queue empty
358
+
359
+ **Side Effects**:
360
+ - Removes message from queue (atomic operation)
361
+ - Marks message as consumed
362
+ - Records consumer and consumption timestamp
363
+
364
+ **Example**:
365
+ ```ruby
366
+ # Consumer loop
367
+ loop do
368
+ msg = memory.consume_message("tasks", "worker_1")
369
+ break unless msg
370
+
371
+ puts "Processing: #{msg[:content][:task_name]} (priority #{msg[:priority]})"
372
+ puts "Sent by: #{msg[:sender]} at #{msg[:posted_at]}"
373
+
374
+ # Process message...
375
+ process_task(msg[:content])
376
+ end
377
+ ```
378
+
379
+ **Thread Safety**: Atomic pop (safe for concurrent consumers with PostgreSQL/Redis).
380
+
381
+ ---
382
+
383
+ #### `peek_messages(topic, limit: 10)`
384
+
385
+ Views messages in queue without consuming them.
386
+
387
+ **Parameters**:
388
+ - `topic` (String) - Topic to peek
389
+ - `limit` (Integer, optional) - Max messages to return (default: 10)
390
+
391
+ **Returns**: `Array<Hash>` - Array of message hashes (same format as `consume_message`)
392
+
393
+ **Example**:
394
+ ```ruby
395
+ # Check queue depth
396
+ pending = memory.peek_messages("tasks", limit: 100)
397
+ puts "Pending tasks: #{pending.size}"
398
+
399
+ # Inspect high-priority messages
400
+ pending.each do |msg|
401
+ if msg[:priority] > 50
402
+ puts "High priority: #{msg[:content][:task_name]}"
403
+ end
404
+ end
405
+ ```
406
+
407
+ **Use Cases**:
408
+ - Monitor queue depth
409
+ - Inspect waiting messages
410
+ - Debugging message flow
411
+
412
+ ---
413
+
414
+ ### Audit Log Methods
415
+
416
+ #### `log_rule_firing(rule_name, fact_uuids, bindings = {})`
417
+
418
+ Logs a rule firing event.
419
+
420
+ **Parameters**:
421
+ - `rule_name` (String) - Name of fired rule
422
+ - `fact_uuids` (Array<String>) - UUIDs of facts that matched
423
+ - `bindings` (Hash, optional) - Variable bindings (default: `{}`)
424
+
425
+ **Returns**: `nil`
426
+
427
+ **Side Effects**:
428
+ - Adds entry to audit log
429
+ - Records timestamp and session ID
430
+
431
+ **Example**:
432
+ ```ruby
433
+ # Typically called by engine, but can be called manually
434
+ memory.log_rule_firing(
435
+ "high_temperature_alert",
436
+ [fact1.uuid, fact2.uuid],
437
+ { :temp? => 85, :location? => "server_room" }
438
+ )
439
+ ```
440
+
441
+ **Note**: `KBS::Blackboard::Engine` calls this automatically. Manual calls useful for custom logging.
442
+
443
+ ---
444
+
445
+ #### `get_history(fact_uuid = nil, limit: 100)`
446
+
447
+ Retrieves fact change history.
448
+
449
+ **Parameters**:
450
+ - `fact_uuid` (String, optional) - Filter by fact UUID (default: `nil`, all facts)
451
+ - `limit` (Integer, optional) - Max entries to return (default: 100)
452
+
453
+ **Returns**: `Array<Hash>` - Array of history entries with `:fact_uuid`, `:fact_type`, `:attributes`, `:action`, `:timestamp`, `:session_id`
454
+
455
+ **Example**:
456
+ ```ruby
457
+ # Get history for specific fact
458
+ fact = memory.add_fact(:temperature, value: 85)
459
+ memory.update_fact(fact, value: 90)
460
+ memory.update_fact(fact, value: 95)
461
+
462
+ history = memory.get_history(fact.uuid)
463
+ history.each do |entry|
464
+ puts "#{entry[:timestamp]}: #{entry[:action]} - #{entry[:attributes][:value]}"
465
+ end
466
+
467
+ # Output:
468
+ # 2025-01-15 10:30:03: UPDATE - 95
469
+ # 2025-01-15 10:30:02: UPDATE - 90
470
+ # 2025-01-15 10:30:00: ADD - 85
471
+ ```
472
+
473
+ **All Facts History**:
474
+ ```ruby
475
+ # Get recent changes across all facts
476
+ recent_changes = memory.get_history(limit: 50)
477
+ ```
478
+
479
+ ---
480
+
481
+ #### `get_rule_firings(rule_name = nil, limit: 100)`
482
+
483
+ Retrieves rule firing history.
484
+
485
+ **Parameters**:
486
+ - `rule_name` (String, optional) - Filter by rule name (default: `nil`, all rules)
487
+ - `limit` (Integer, optional) - Max entries to return (default: 100)
488
+
489
+ **Returns**: `Array<Hash>` - Array of firing entries with `:rule_name`, `:fact_uuids`, `:bindings`, `:fired_at`, `:session_id`
490
+
491
+ **Example**:
492
+ ```ruby
493
+ # Get firings for specific rule
494
+ firings = memory.get_rule_firings("high_temperature_alert", limit: 10)
495
+ firings.each do |firing|
496
+ puts "#{firing[:fired_at]}: #{firing[:rule_name]}"
497
+ puts " Bindings: #{firing[:bindings]}"
498
+ puts " Facts: #{firing[:fact_uuids]}"
499
+ end
500
+
501
+ # All rule firings
502
+ all_firings = memory.get_rule_firings(limit: 100)
503
+ ```
504
+
505
+ **Use Cases**:
506
+ - Debugging rule behavior
507
+ - Performance analysis
508
+ - Compliance auditing
509
+
510
+ ---
511
+
512
+ ### Knowledge Source Methods
513
+
514
+ #### `register_knowledge_source(name, description: nil, topics: [])`
515
+
516
+ Registers an agent/knowledge source.
517
+
518
+ **Parameters**:
519
+ - `name` (String) - Knowledge source name
520
+ - `description` (String, optional) - Description (default: `nil`)
521
+ - `topics` (Array<String>, optional) - Topics this source produces/consumes (default: `[]`)
522
+
523
+ **Returns**: `nil`
524
+
525
+ **Side Effects**:
526
+ - Stores knowledge source metadata in database
527
+
528
+ **Example**:
529
+ ```ruby
530
+ memory.register_knowledge_source(
531
+ "TemperatureMonitor",
532
+ description: "Monitors temperature sensors and generates alerts",
533
+ topics: ["temperature_readings", "alerts"]
534
+ )
535
+
536
+ memory.register_knowledge_source(
537
+ "AlertDispatcher",
538
+ description: "Dispatches alerts to external systems",
539
+ topics: ["alerts"]
540
+ )
541
+ ```
542
+
543
+ **Use Cases**:
544
+ - Document multi-agent systems
545
+ - Visualize agent architecture
546
+ - Track message flow
547
+
548
+ ---
549
+
550
+ ### Observer Pattern Methods
551
+
552
+ #### `add_observer(observer)`
553
+
554
+ Registers an observer to receive fact change notifications.
555
+
556
+ **Parameters**:
557
+ - `observer` - Object responding to `update(action, fact)` method
558
+
559
+ **Returns**: `nil`
560
+
561
+ **Side Effects**: Adds observer to internal observers list
562
+
563
+ **Example**:
564
+ ```ruby
565
+ class FactLogger
566
+ def update(action, fact)
567
+ case action
568
+ when :add
569
+ puts "Added: #{fact.type} #{fact.attributes}"
570
+ when :remove
571
+ puts "Removed: #{fact.uuid}"
572
+ end
573
+ end
574
+ end
575
+
576
+ logger = FactLogger.new
577
+ memory.add_observer(logger)
578
+
579
+ memory.add_fact(:temperature, value: 85)
580
+ # Output: Added: temperature {:value=>85}
581
+ ```
582
+
583
+ **Important**: Observers are NOT persisted. Re-register after restart.
584
+
585
+ ---
586
+
587
+ ### Session Management Methods
588
+
589
+ #### `clear_session`
590
+
591
+ Removes all facts from current session.
592
+
593
+ **Parameters**: None
594
+
595
+ **Returns**: `nil`
596
+
597
+ **Side Effects**:
598
+ - Removes facts with matching session_id
599
+ - Preserves audit log
600
+
601
+ **Example**:
602
+ ```ruby
603
+ # Add facts
604
+ memory.add_fact(:temperature, value: 85)
605
+ memory.add_fact(:humidity, value: 60)
606
+
607
+ # Clear session facts
608
+ memory.clear_session
609
+
610
+ # Facts removed, but audit log intact
611
+ puts memory.facts.size # => 0
612
+ puts memory.get_history.size # => 2 (ADD entries still present)
613
+ ```
614
+
615
+ ---
616
+
617
+ #### `transaction(&block)`
618
+
619
+ Executes block within database transaction.
620
+
621
+ **Parameters**:
622
+ - `&block` - Block to execute
623
+
624
+ **Returns**: Result of block
625
+
626
+ **Side Effects**:
627
+ - Begins transaction
628
+ - Executes block
629
+ - Commits on success
630
+ - Rolls back on exception
631
+
632
+ **Example**:
633
+ ```ruby
634
+ memory.transaction do
635
+ fact1 = memory.add_fact(:order, id: 1, total: 100)
636
+ fact2 = memory.add_fact(:inventory, item: "ABC", quantity: 10)
637
+
638
+ # If this raises, both facts are rolled back
639
+ raise "Validation failed" if fact1[:total] > 1000
640
+ end
641
+ ```
642
+
643
+ **Nested Transactions**: Supported (SQLite uses savepoints).
644
+
645
+ ---
646
+
647
+ ### Statistics Methods
648
+
649
+ #### `stats`
650
+
651
+ Returns blackboard statistics.
652
+
653
+ **Parameters**: None
654
+
655
+ **Returns**: `Hash` with keys:
656
+ - `:facts_count` (Integer) - Active facts
657
+ - `:total_messages` (Integer) - Total messages (consumed + unconsumed)
658
+ - `:unconsumed_messages` (Integer) - Unconsumed messages
659
+ - `:rules_fired` (Integer) - Total rule firings
660
+
661
+ **Example**:
662
+ ```ruby
663
+ stats = memory.stats
664
+ puts "Facts: #{stats[:facts_count]}"
665
+ puts "Messages (unconsumed): #{stats[:unconsumed_messages]}"
666
+ puts "Messages (total): #{stats[:total_messages]}"
667
+ puts "Rules fired: #{stats[:rules_fired]}"
668
+ ```
669
+
670
+ ---
671
+
672
+ ### Maintenance Methods
673
+
674
+ #### `vacuum`
675
+
676
+ Optimizes database storage (SQLite only).
677
+
678
+ **Parameters**: None
679
+
680
+ **Returns**: `nil`
681
+
682
+ **Side Effects**: Reclaims unused database space
683
+
684
+ **Example**:
685
+ ```ruby
686
+ # After deleting many facts
687
+ memory.vacuum
688
+ ```
689
+
690
+ **When to Use**: After bulk deletions or periodically for long-running systems.
691
+
692
+ ---
693
+
694
+ #### `close`
695
+
696
+ Closes database connection.
697
+
698
+ **Parameters**: None
699
+
700
+ **Returns**: `nil`
701
+
702
+ **Side Effects**: Closes connection to store
703
+
704
+ **Example**:
705
+ ```ruby
706
+ memory = KBS::Blackboard::Memory.new(db_path: 'kb.db')
707
+ # ... use memory ...
708
+ memory.close
709
+ ```
710
+
711
+ **Important**: Required for proper cleanup. Use `ensure` block:
712
+ ```ruby
713
+ memory = KBS::Blackboard::Memory.new(db_path: 'kb.db')
714
+ begin
715
+ # ... use memory ...
716
+ ensure
717
+ memory.close
718
+ end
719
+ ```
720
+
721
+ ---
722
+
723
+ ## KBS::Blackboard::MessageQueue
724
+
725
+ Priority-based message queue for inter-agent communication.
726
+
727
+ **Typically accessed via**: `memory.message_queue` or `memory.post_message()` / `memory.consume_message()`
728
+
729
+ ### Methods
730
+
731
+ #### `post(sender, topic, content, priority: 0)`
732
+
733
+ Posts a message to the queue.
734
+
735
+ **Parameters**:
736
+ - `sender` (String) - Sender identifier
737
+ - `topic` (String) - Message topic
738
+ - `content` (Hash or String) - Message payload (auto-converts to JSON)
739
+ - `priority` (Integer, optional) - Priority (default: 0)
740
+
741
+ **Returns**: `nil`
742
+
743
+ **Example**:
744
+ ```ruby
745
+ memory.message_queue.post("agent1", "alerts", { alert: "critical" }, priority: 100)
746
+ ```
747
+
748
+ ---
749
+
750
+ #### `consume(topic, consumer)`
751
+
752
+ Consumes highest priority message from topic.
753
+
754
+ **Parameters**:
755
+ - `topic` (String) - Topic to consume from
756
+ - `consumer` (String) - Consumer identifier
757
+
758
+ **Returns**: `Hash` or `nil`
759
+
760
+ **Example**:
761
+ ```ruby
762
+ msg = memory.message_queue.consume("tasks", "worker_1")
763
+ puts msg[:content] if msg
764
+ ```
765
+
766
+ ---
767
+
768
+ #### `peek(topic, limit: 10)`
769
+
770
+ Views messages without consuming.
771
+
772
+ **Parameters**:
773
+ - `topic` (String) - Topic to peek
774
+ - `limit` (Integer, optional) - Max messages (default: 10)
775
+
776
+ **Returns**: `Array<Hash>`
777
+
778
+ **Example**:
779
+ ```ruby
780
+ pending = memory.message_queue.peek("tasks", limit: 5)
781
+ puts "Next #{pending.size} tasks:"
782
+ pending.each { |m| puts " - #{m[:content]}" }
783
+ ```
784
+
785
+ ---
786
+
787
+ #### `stats`
788
+
789
+ Returns queue statistics.
790
+
791
+ **Returns**: `Hash` with `:total_messages`, `:unconsumed_messages`
792
+
793
+ **Example**:
794
+ ```ruby
795
+ stats = memory.message_queue.stats
796
+ puts "Queue depth: #{stats[:unconsumed_messages]}"
797
+ ```
798
+
799
+ ---
800
+
801
+ ## KBS::Blackboard::AuditLog
802
+
803
+ Complete audit trail of all fact changes and rule firings.
804
+
805
+ **Typically accessed via**: `memory.audit_log` or `memory.get_history()` / `memory.get_rule_firings()`
806
+
807
+ ### Methods
808
+
809
+ #### `log_fact_change(fact_uuid, fact_type, attributes, action)`
810
+
811
+ Logs a fact change event.
812
+
813
+ **Parameters**:
814
+ - `fact_uuid` (String) - Fact UUID
815
+ - `fact_type` (Symbol) - Fact type
816
+ - `attributes` (Hash) - Fact attributes
817
+ - `action` (String) - Action: "ADD", "UPDATE", "REMOVE"
818
+
819
+ **Returns**: `nil`
820
+
821
+ **Example**:
822
+ ```ruby
823
+ memory.audit_log.log_fact_change(
824
+ fact.uuid,
825
+ :temperature,
826
+ { value: 85 },
827
+ 'ADD'
828
+ )
829
+ ```
830
+
831
+ **Note**: Automatically called by Memory. Manual calls useful for custom tracking.
832
+
833
+ ---
834
+
835
+ #### `log_rule_firing(rule_name, fact_uuids, bindings = {})`
836
+
837
+ Logs a rule firing event.
838
+
839
+ **Parameters**:
840
+ - `rule_name` (String) - Rule name
841
+ - `fact_uuids` (Array<String>) - Matched fact UUIDs
842
+ - `bindings` (Hash, optional) - Variable bindings (default: `{}`)
843
+
844
+ **Returns**: `nil`
845
+
846
+ **Example**:
847
+ ```ruby
848
+ memory.audit_log.log_rule_firing(
849
+ "high_temp_alert",
850
+ [fact1.uuid, fact2.uuid],
851
+ { :temp? => 85 }
852
+ )
853
+ ```
854
+
855
+ ---
856
+
857
+ #### `fact_history(fact_uuid = nil, limit: 100)`
858
+
859
+ Retrieves fact change history.
860
+
861
+ **Parameters**:
862
+ - `fact_uuid` (String, optional) - Filter by UUID (default: `nil`)
863
+ - `limit` (Integer, optional) - Max entries (default: 100)
864
+
865
+ **Returns**: `Array<Hash>`
866
+
867
+ **Example**:
868
+ ```ruby
869
+ history = memory.audit_log.fact_history(fact.uuid, limit: 10)
870
+ ```
871
+
872
+ ---
873
+
874
+ #### `rule_firings(rule_name = nil, limit: 100)`
875
+
876
+ Retrieves rule firing history.
877
+
878
+ **Parameters**:
879
+ - `rule_name` (String, optional) - Filter by rule name (default: `nil`)
880
+ - `limit` (Integer, optional) - Max entries (default: 100)
881
+
882
+ **Returns**: `Array<Hash>`
883
+
884
+ **Example**:
885
+ ```ruby
886
+ firings = memory.audit_log.rule_firings("my_rule", limit: 50)
887
+ ```
888
+
889
+ ---
890
+
891
+ #### `stats`
892
+
893
+ Returns audit log statistics.
894
+
895
+ **Returns**: `Hash` with `:rules_fired`
896
+
897
+ **Example**:
898
+ ```ruby
899
+ stats = memory.audit_log.stats
900
+ puts "Total rule firings: #{stats[:rules_fired]}"
901
+ ```
902
+
903
+ ---
904
+
905
+ ## Usage Patterns
906
+
907
+ ### 1. Multi-Agent Coordination
908
+
909
+ ```ruby
910
+ # Setup
911
+ memory = KBS::Blackboard::Memory.new(db_path: 'agents.db')
912
+
913
+ # Agent 1 - Temperature Monitor
914
+ memory.register_knowledge_source(
915
+ "TempMonitor",
916
+ description: "Monitors temperature sensors",
917
+ topics: ["sensors", "alerts"]
918
+ )
919
+
920
+ def monitor_loop(memory)
921
+ loop do
922
+ temp = read_sensor
923
+ fact = memory.add_fact(:temperature, value: temp, timestamp: Time.now)
924
+
925
+ if temp > 80
926
+ memory.post_message(
927
+ "TempMonitor",
928
+ "alerts",
929
+ { type: "high_temp", value: temp },
930
+ priority: 50
931
+ )
932
+ end
933
+
934
+ sleep 5
935
+ end
936
+ end
937
+
938
+ # Agent 2 - Alert Dispatcher
939
+ memory.register_knowledge_source(
940
+ "AlertDispatcher",
941
+ description: "Sends alerts to external systems",
942
+ topics: ["alerts"]
943
+ )
944
+
945
+ def dispatch_loop(memory)
946
+ loop do
947
+ msg = memory.consume_message("alerts", "AlertDispatcher")
948
+ break unless msg
949
+
950
+ case msg[:content][:type]
951
+ when "high_temp"
952
+ send_email_alert(msg[:content][:value])
953
+ end
954
+ end
955
+ end
956
+ ```
957
+
958
+ ---
959
+
960
+ ### 2. Audit Trail Analysis
961
+
962
+ ```ruby
963
+ # Find facts that were updated multiple times
964
+ memory.get_history(limit: 1000).group_by { |e| e[:fact_uuid] }.each do |uuid, entries|
965
+ if entries.size > 5
966
+ puts "Fact #{uuid} changed #{entries.size} times"
967
+ entries.each do |entry|
968
+ puts " #{entry[:timestamp]}: #{entry[:action]} - #{entry[:attributes]}"
969
+ end
970
+ end
971
+ end
972
+ ```
973
+
974
+ ---
975
+
976
+ ### 3. Rule Performance Analysis
977
+
978
+ ```ruby
979
+ # Analyze rule firing frequency
980
+ firings = memory.get_rule_firings(limit: 10000)
981
+ by_rule = firings.group_by { |f| f[:rule_name] }
982
+
983
+ by_rule.each do |rule_name, firings_list|
984
+ puts "#{rule_name}: #{firings_list.size} firings"
985
+
986
+ # Calculate average time between firings
987
+ if firings_list.size > 1
988
+ times = firings_list.map { |f| f[:fired_at] }.sort
989
+ intervals = times.each_cons(2).map { |t1, t2| (t2 - t1).to_f }
990
+ avg_interval = intervals.sum / intervals.size
991
+ puts " Avg interval: #{avg_interval.round(2)} seconds"
992
+ end
993
+ end
994
+ ```
995
+
996
+ ---
997
+
998
+ ### 4. Transaction-Based Workflows
999
+
1000
+ ```ruby
1001
+ def process_order(memory, order_data)
1002
+ memory.transaction do
1003
+ # Add order fact
1004
+ order = memory.add_fact(:order, order_data)
1005
+
1006
+ # Check inventory
1007
+ inventory = memory.get_facts(:inventory, product_id: order[:product_id]).first
1008
+ raise "Insufficient inventory" if inventory[:quantity] < order[:quantity]
1009
+
1010
+ # Deduct inventory
1011
+ memory.update_fact(inventory, quantity: inventory[:quantity] - order[:quantity])
1012
+
1013
+ # Create shipment fact
1014
+ shipment = memory.add_fact(:shipment, order_id: order.uuid, status: "pending")
1015
+
1016
+ # Post message for shipping agent
1017
+ memory.post_message(
1018
+ "OrderProcessor",
1019
+ "shipments",
1020
+ { shipment_id: shipment.uuid },
1021
+ priority: 10
1022
+ )
1023
+
1024
+ # If any step fails, entire transaction rolls back
1025
+ end
1026
+ end
1027
+ ```
1028
+
1029
+ ---
1030
+
1031
+ ### 5. Debugging Message Flow
1032
+
1033
+ ```ruby
1034
+ # Monitor message queue
1035
+ def monitor_queue(memory, topic)
1036
+ loop do
1037
+ pending = memory.peek_messages(topic, limit: 10)
1038
+ puts "#{Time.now}: #{pending.size} messages in #{topic} queue"
1039
+
1040
+ pending.each do |msg|
1041
+ age = Time.now - msg[:posted_at]
1042
+ puts " [#{msg[:priority]}] #{msg[:sender]}: #{msg[:content]} (#{age.round}s old)"
1043
+ end
1044
+
1045
+ sleep 5
1046
+ end
1047
+ end
1048
+ ```
1049
+
1050
+ ---
1051
+
1052
+ ### 6. Session Isolation
1053
+
1054
+ ```ruby
1055
+ # Separate test runs
1056
+ test_memory = KBS::Blackboard::Memory.new(db_path: 'test.db')
1057
+ puts "Session: #{test_memory.session_id}"
1058
+
1059
+ # Run test
1060
+ test_memory.add_fact(:test_marker, run_id: 1)
1061
+ run_tests(test_memory)
1062
+
1063
+ # Cleanup session (preserves audit log)
1064
+ test_memory.clear_session
1065
+
1066
+ # Analyze audit log across sessions
1067
+ all_history = test_memory.get_history(limit: 10000)
1068
+ by_session = all_history.group_by { |e| e[:session_id] }
1069
+ puts "Total sessions: #{by_session.size}"
1070
+ ```
1071
+
1072
+ ---
1073
+
1074
+ ### 7. Custom Observer for Metrics
1075
+
1076
+ ```ruby
1077
+ class MetricsObserver
1078
+ def initialize
1079
+ @fact_counts = Hash.new(0)
1080
+ @add_count = 0
1081
+ @remove_count = 0
1082
+ end
1083
+
1084
+ def update(action, fact)
1085
+ case action
1086
+ when :add
1087
+ @add_count += 1
1088
+ @fact_counts[fact.type] += 1
1089
+ when :remove
1090
+ @remove_count += 1
1091
+ @fact_counts[fact.type] -= 1
1092
+ end
1093
+ end
1094
+
1095
+ def report
1096
+ puts "Facts added: #{@add_count}"
1097
+ puts "Facts removed: #{@remove_count}"
1098
+ puts "Active facts by type:"
1099
+ @fact_counts.each do |type, count|
1100
+ puts " #{type}: #{count}"
1101
+ end
1102
+ end
1103
+ end
1104
+
1105
+ metrics = MetricsObserver.new
1106
+ memory.add_observer(metrics)
1107
+
1108
+ # ... run system ...
1109
+
1110
+ metrics.report
1111
+ ```
1112
+
1113
+ ---
1114
+
1115
+ ## Performance Considerations
1116
+
1117
+ ### Message Queue
1118
+
1119
+ - **Priority indexing**: Messages sorted by priority + timestamp
1120
+ - **Atomic pop**: `consume` uses SELECT + UPDATE in transaction (safe for concurrent consumers)
1121
+ - **Scaling**: For >10,000 messages/sec, use Redis store
1122
+
1123
+ ### Audit Log
1124
+
1125
+ - **Write performance**: Each fact change = 1 audit log insert (can be disabled for high-throughput)
1126
+ - **Query performance**: Indexed by `fact_uuid` and `session_id`
1127
+ - **Growth**: Audit log grows unbounded. Implement periodic archival for production:
1128
+
1129
+ ```ruby
1130
+ # Archive old audit entries
1131
+ def archive_old_audit(memory, cutoff_date)
1132
+ memory.store.db.execute(
1133
+ "DELETE FROM fact_history WHERE timestamp < ?",
1134
+ [cutoff_date.iso8601]
1135
+ )
1136
+
1137
+ memory.store.db.execute(
1138
+ "DELETE FROM rules_fired WHERE fired_at < ?",
1139
+ [cutoff_date.iso8601]
1140
+ )
1141
+
1142
+ memory.vacuum
1143
+ end
1144
+
1145
+ # Archive entries older than 30 days
1146
+ archive_old_audit(memory, Date.today - 30)
1147
+ ```
1148
+
1149
+ ---
1150
+
1151
+ ## See Also
1152
+
1153
+ - [Engine API](engine.md) - Blackboard::Engine integration
1154
+ - [Facts API](facts.md) - Persistent fact objects
1155
+ - [Custom Persistence](../advanced/custom-persistence.md) - Implementing custom stores
1156
+ - [Blackboard Guide](../guides/blackboard-memory.md) - Blackboard pattern overview
1157
+ - [Multi-Agent Example](../examples/multi-agent.md) - Multi-agent coordination