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,1335 @@
1
+ # Multi-Agent Systems
2
+
3
+ Build collaborative multi-agent systems using KBS blackboard memory for coordination, message passing, and distributed reasoning.
4
+
5
+ ## System Overview
6
+
7
+ This example demonstrates a smart home automation system with:
8
+
9
+ - **Multiple Specialized Agents** - Temperature, security, energy, scheduling
10
+ - **Blackboard Coordination** - Shared persistent workspace
11
+ - **Message Passing** - Inter-agent communication via priority queues
12
+ - **Conflict Resolution** - Arbitration when agents disagree
13
+ - **Emergent Behavior** - Complex system behavior from simple agent rules
14
+
15
+ ## Architecture
16
+
17
+ ```
18
+ ┌─────────────────── Blackboard Memory ──────────────────┐
19
+ │ │
20
+ │ Facts: sensor_reading, alert, command, agent_status │
21
+ │ Messages: priority queues for agent communication │
22
+ │ Audit: complete history of all agent actions │
23
+ │ │
24
+ └──────────────────────┬──────────────────────────────────┘
25
+
26
+ ┌──────────────┼──────────────┬──────────────┐
27
+ │ │ │ │
28
+ ┌─────▼────┐ ┌────▼─────┐ ┌────▼─────┐ ┌─────▼────┐
29
+ │ Temp │ │ Security │ │ Energy │ │ Schedule │
30
+ │ Agent │ │ Agent │ │ Agent │ │ Agent │
31
+ └──────────┘ └──────────┘ └──────────┘ └──────────┘
32
+ ```
33
+
34
+ ## Complete Implementation
35
+
36
+ ### Multi-Agent Smart Home
37
+
38
+ ```ruby
39
+ require 'kbs'
40
+
41
+ # Base agent class
42
+ class Agent
43
+ attr_reader :name, :engine
44
+
45
+ def initialize(name, engine)
46
+ @name = name
47
+ @engine = engine
48
+ @running = false
49
+ end
50
+
51
+ def start
52
+ @running = true
53
+ @engine.add_fact(:agent_status, {
54
+ agent: @name,
55
+ status: "started",
56
+ timestamp: Time.now
57
+ })
58
+ end
59
+
60
+ def stop
61
+ @running = false
62
+ @engine.add_fact(:agent_status, {
63
+ agent: @name,
64
+ status: "stopped",
65
+ timestamp: Time.now
66
+ })
67
+ end
68
+
69
+ def running?
70
+ @running
71
+ end
72
+
73
+ def send_message(topic, content, priority: 50)
74
+ @engine.send_message(topic, {
75
+ from: @name,
76
+ content: content,
77
+ timestamp: Time.now
78
+ }, priority: priority)
79
+ end
80
+
81
+ def receive_messages(topic)
82
+ messages = []
83
+ while (msg = @engine.pop_message(topic))
84
+ messages << msg
85
+ end
86
+ messages
87
+ end
88
+
89
+ # Override in subclasses
90
+ def process
91
+ raise NotImplementedError, "Subclass must implement process"
92
+ end
93
+
94
+ def run_cycle
95
+ return unless running?
96
+ process
97
+ end
98
+ end
99
+
100
+ # Temperature control agent
101
+ class TemperatureAgent < Agent
102
+ def initialize(engine, target_temp: 22.0)
103
+ super("TemperatureAgent", engine)
104
+ @target_temp = target_temp
105
+ setup_rules
106
+ end
107
+
108
+ def setup_rules
109
+ # Rule 1: Detect high temperature
110
+ high_temp_rule = KBS::Rule.new("detect_high_temperature", priority: 100) do |r|
111
+ r.conditions = [
112
+ KBS::Condition.new(:sensor_reading, {
113
+ type: "temperature",
114
+ location: :location?,
115
+ value: :temp?
116
+ }, predicate: lambda { |f| f[:value] > @target_temp + 2 }),
117
+
118
+ KBS::Condition.new(:temperature_alert, {
119
+ location: :location?
120
+ }, negated: true)
121
+ ]
122
+
123
+ r.action = lambda do |facts, bindings|
124
+ @engine.add_fact(:temperature_alert, {
125
+ location: bindings[:location?],
126
+ temperature: bindings[:temp?],
127
+ severity: "high",
128
+ timestamp: Time.now
129
+ })
130
+
131
+ send_message(:hvac_control, {
132
+ action: "cool",
133
+ location: bindings[:location?],
134
+ target: @target_temp
135
+ }, priority: 80)
136
+ end
137
+ end
138
+
139
+ # Rule 2: Detect low temperature
140
+ low_temp_rule = KBS::Rule.new("detect_low_temperature", priority: 100) do |r|
141
+ r.conditions = [
142
+ KBS::Condition.new(:sensor_reading, {
143
+ type: "temperature",
144
+ location: :location?,
145
+ value: :temp?
146
+ }, predicate: lambda { |f| f[:value] < @target_temp - 2 }),
147
+
148
+ KBS::Condition.new(:temperature_alert, {
149
+ location: :location?
150
+ }, negated: true)
151
+ ]
152
+
153
+ r.action = lambda do |facts, bindings|
154
+ @engine.add_fact(:temperature_alert, {
155
+ location: bindings[:location?],
156
+ temperature: bindings[:temp?],
157
+ severity: "low",
158
+ timestamp: Time.now
159
+ })
160
+
161
+ send_message(:hvac_control, {
162
+ action: "heat",
163
+ location: bindings[:location?],
164
+ target: @target_temp
165
+ }, priority: 80)
166
+ end
167
+ end
168
+
169
+ # Rule 3: Clear alert when temperature normalized
170
+ clear_alert_rule = KBS::Rule.new("clear_temperature_alert", priority: 90) do |r|
171
+ r.conditions = [
172
+ KBS::Condition.new(:sensor_reading, {
173
+ type: "temperature",
174
+ location: :location?,
175
+ value: :temp?
176
+ }, predicate: lambda { |f|
177
+ (f[:value] - @target_temp).abs <= 1
178
+ }),
179
+
180
+ KBS::Condition.new(:temperature_alert, {
181
+ location: :location?
182
+ })
183
+ ]
184
+
185
+ r.action = lambda do |facts, bindings|
186
+ alert = facts.find { |f|
187
+ f.type == :temperature_alert && f[:location] == bindings[:location?]
188
+ }
189
+ @engine.remove_fact(alert) if alert
190
+
191
+ send_message(:hvac_control, {
192
+ action: "off",
193
+ location: bindings[:location?]
194
+ }, priority: 50)
195
+ end
196
+ end
197
+
198
+ @engine.add_rule(high_temp_rule)
199
+ @engine.add_rule(low_temp_rule)
200
+ @engine.add_rule(clear_alert_rule)
201
+ end
202
+
203
+ def process
204
+ @engine.run
205
+ end
206
+ end
207
+
208
+ # Security monitoring agent
209
+ class SecurityAgent < Agent
210
+ def initialize(engine)
211
+ super("SecurityAgent", engine)
212
+ setup_rules
213
+ end
214
+
215
+ def setup_rules
216
+ # Rule 1: Detect intrusion
217
+ intrusion_rule = KBS::Rule.new("detect_intrusion", priority: 100) do |r|
218
+ r.conditions = [
219
+ KBS::Condition.new(:sensor_reading, {
220
+ type: "motion",
221
+ location: :location?,
222
+ detected: true
223
+ }),
224
+
225
+ KBS::Condition.new(:occupancy, {
226
+ status: "away"
227
+ }),
228
+
229
+ KBS::Condition.new(:security_alert, {
230
+ type: "intrusion",
231
+ location: :location?
232
+ }, negated: true)
233
+ ]
234
+
235
+ r.action = lambda do |facts, bindings|
236
+ @engine.add_fact(:security_alert, {
237
+ type: "intrusion",
238
+ location: bindings[:location?],
239
+ severity: "critical",
240
+ timestamp: Time.now
241
+ })
242
+
243
+ send_message(:security_system, {
244
+ action: "alarm",
245
+ location: bindings[:location?]
246
+ }, priority: 100)
247
+
248
+ send_message(:notifications, {
249
+ type: "security",
250
+ message: "Intrusion detected at #{bindings[:location?]}"
251
+ }, priority: 100)
252
+ end
253
+ end
254
+
255
+ # Rule 2: Door left open
256
+ door_open_rule = KBS::Rule.new("detect_door_open", priority: 90) do |r|
257
+ r.conditions = [
258
+ KBS::Condition.new(:sensor_reading, {
259
+ type: "door",
260
+ location: :location?,
261
+ state: "open",
262
+ timestamp: :time?
263
+ }, predicate: lambda { |f|
264
+ (Time.now - f[:timestamp]) > 300 # Open for 5 minutes
265
+ }),
266
+
267
+ KBS::Condition.new(:security_alert, {
268
+ type: "door_open",
269
+ location: :location?
270
+ }, negated: true)
271
+ ]
272
+
273
+ r.action = lambda do |facts, bindings|
274
+ @engine.add_fact(:security_alert, {
275
+ type: "door_open",
276
+ location: bindings[:location?],
277
+ severity: "medium",
278
+ timestamp: Time.now
279
+ })
280
+
281
+ send_message(:notifications, {
282
+ type: "security",
283
+ message: "Door at #{bindings[:location?]} left open"
284
+ }, priority: 70)
285
+ end
286
+ end
287
+
288
+ @engine.add_rule(intrusion_rule)
289
+ @engine.add_rule(door_open_rule)
290
+ end
291
+
292
+ def process
293
+ @engine.run
294
+ end
295
+ end
296
+
297
+ # Energy management agent
298
+ class EnergyAgent < Agent
299
+ def initialize(engine, max_usage: 5000)
300
+ super("EnergyAgent", engine)
301
+ @max_usage = max_usage # watts
302
+ setup_rules
303
+ end
304
+
305
+ def setup_rules
306
+ # Rule 1: High energy consumption
307
+ high_consumption_rule = KBS::Rule.new("detect_high_consumption", priority: 80) do |r|
308
+ r.conditions = [
309
+ KBS::Condition.new(:sensor_reading, {
310
+ type: "power",
311
+ value: :usage?
312
+ }, predicate: lambda { |f| f[:value] > @max_usage }),
313
+
314
+ KBS::Condition.new(:energy_alert, {
315
+ type: "high_consumption"
316
+ }, negated: true)
317
+ ]
318
+
319
+ r.action = lambda do |facts, bindings|
320
+ @engine.add_fact(:energy_alert, {
321
+ type: "high_consumption",
322
+ usage: bindings[:usage?],
323
+ limit: @max_usage,
324
+ timestamp: Time.now
325
+ })
326
+
327
+ # Request non-essential devices to reduce consumption
328
+ send_message(:device_control, {
329
+ action: "reduce_consumption",
330
+ priority_level: "low"
331
+ }, priority: 60)
332
+
333
+ send_message(:notifications, {
334
+ type: "energy",
335
+ message: "High energy usage: #{bindings[:usage?]}W (limit: #{@max_usage}W)"
336
+ }, priority: 60)
337
+ end
338
+ end
339
+
340
+ # Rule 2: Coordinate with HVAC during high usage
341
+ hvac_coordination_rule = KBS::Rule.new("coordinate_hvac_energy", priority: 75) do |r|
342
+ r.conditions = [
343
+ KBS::Condition.new(:energy_alert, {
344
+ type: "high_consumption"
345
+ }),
346
+
347
+ KBS::Condition.new(:temperature_alert, {
348
+ location: :location?
349
+ })
350
+ ]
351
+
352
+ r.action = lambda do |facts, bindings|
353
+ # Ask temperature agent to reduce HVAC intensity
354
+ send_message(:hvac_control, {
355
+ action: "eco_mode",
356
+ location: bindings[:location?]
357
+ }, priority: 70)
358
+ end
359
+ end
360
+
361
+ @engine.add_rule(high_consumption_rule)
362
+ @engine.add_rule(hvac_coordination_rule)
363
+ end
364
+
365
+ def process
366
+ @engine.run
367
+ end
368
+ end
369
+
370
+ # Scheduling agent
371
+ class ScheduleAgent < Agent
372
+ def initialize(engine)
373
+ super("ScheduleAgent", engine)
374
+ setup_rules
375
+ end
376
+
377
+ def setup_rules
378
+ # Rule 1: Morning routine
379
+ morning_rule = KBS::Rule.new("morning_routine", priority: 70) do |r|
380
+ r.conditions = [
381
+ KBS::Condition.new(:time_event, {
382
+ event: "morning",
383
+ hour: :hour?
384
+ }),
385
+
386
+ KBS::Condition.new(:routine_executed, {
387
+ type: "morning"
388
+ }, negated: true)
389
+ ]
390
+
391
+ r.action = lambda do |facts, bindings|
392
+ @engine.add_fact(:routine_executed, {
393
+ type: "morning",
394
+ timestamp: Time.now
395
+ })
396
+
397
+ # Update occupancy
398
+ @engine.add_fact(:occupancy, { status: "home" })
399
+
400
+ # Adjust temperature
401
+ send_message(:temperature_control, {
402
+ action: "set_target",
403
+ temperature: 22
404
+ }, priority: 50)
405
+
406
+ # Turn on lights
407
+ send_message(:device_control, {
408
+ action: "lights_on",
409
+ locations: ["bedroom", "kitchen"]
410
+ }, priority: 40)
411
+ end
412
+ end
413
+
414
+ # Rule 2: Night routine
415
+ night_rule = KBS::Rule.new("night_routine", priority: 70) do |r|
416
+ r.conditions = [
417
+ KBS::Condition.new(:time_event, {
418
+ event: "night",
419
+ hour: :hour?
420
+ }),
421
+
422
+ KBS::Condition.new(:routine_executed, {
423
+ type: "night"
424
+ }, negated: true)
425
+ ]
426
+
427
+ r.action = lambda do |facts, bindings|
428
+ @engine.add_fact(:routine_executed, {
429
+ type: "night",
430
+ timestamp: Time.now
431
+ })
432
+
433
+ # Update occupancy
434
+ @engine.add_fact(:occupancy, { status: "sleeping" })
435
+
436
+ # Lower temperature
437
+ send_message(:temperature_control, {
438
+ action: "set_target",
439
+ temperature: 18
440
+ }, priority: 50)
441
+
442
+ # Turn off lights except nightlights
443
+ send_message(:device_control, {
444
+ action: "lights_off",
445
+ exclude: ["bathroom_nightlight"]
446
+ }, priority: 40)
447
+
448
+ # Enable security
449
+ send_message(:security_system, {
450
+ action: "arm_night_mode"
451
+ }, priority: 60)
452
+ end
453
+ end
454
+
455
+ # Rule 3: Away mode
456
+ away_rule = KBS::Rule.new("away_mode", priority: 70) do |r|
457
+ r.conditions = [
458
+ KBS::Condition.new(:time_event, {
459
+ event: "departure"
460
+ }),
461
+
462
+ KBS::Condition.new(:occupancy, {
463
+ status: "away"
464
+ }, negated: true)
465
+ ]
466
+
467
+ r.action = lambda do |facts, bindings|
468
+ old_occupancy = @engine.facts.find { |f| f.type == :occupancy }
469
+ @engine.remove_fact(old_occupancy) if old_occupancy
470
+
471
+ @engine.add_fact(:occupancy, { status: "away" })
472
+
473
+ # Turn off all lights
474
+ send_message(:device_control, {
475
+ action: "all_lights_off"
476
+ }, priority: 40)
477
+
478
+ # Energy saving mode
479
+ send_message(:temperature_control, {
480
+ action: "eco_mode"
481
+ }, priority: 50)
482
+
483
+ # Arm security
484
+ send_message(:security_system, {
485
+ action: "arm_away_mode"
486
+ }, priority: 80)
487
+ end
488
+ end
489
+
490
+ @engine.add_rule(morning_rule)
491
+ @engine.add_rule(night_rule)
492
+ @engine.add_rule(away_rule)
493
+ end
494
+
495
+ def process
496
+ @engine.run
497
+ end
498
+ end
499
+
500
+ # Arbitration agent - resolves conflicts
501
+ class ArbitrationAgent < Agent
502
+ def initialize(engine)
503
+ super("ArbitrationAgent", engine)
504
+ setup_rules
505
+ end
506
+
507
+ def setup_rules
508
+ # Rule: Security overrides energy savings
509
+ security_priority_rule = KBS::Rule.new("security_overrides_energy", priority: 95) do |r|
510
+ r.conditions = [
511
+ KBS::Condition.new(:security_alert, {
512
+ severity: "critical"
513
+ }),
514
+
515
+ KBS::Condition.new(:energy_alert, {
516
+ type: "high_consumption"
517
+ })
518
+ ]
519
+
520
+ r.action = lambda do |facts, bindings|
521
+ # Cancel energy reduction requests
522
+ send_message(:device_control, {
523
+ action: "cancel_energy_reduction",
524
+ reason: "security_priority"
525
+ }, priority: 95)
526
+
527
+ # Notify
528
+ send_message(:notifications, {
529
+ type: "system",
530
+ message: "Security takes priority over energy savings"
531
+ }, priority: 90)
532
+ end
533
+ end
534
+
535
+ # Rule: Comfort overrides energy during occupied hours
536
+ comfort_priority_rule = KBS::Rule.new("comfort_during_occupied", priority: 85) do |r|
537
+ r.conditions = [
538
+ KBS::Condition.new(:occupancy, {
539
+ status: :status?
540
+ }, predicate: lambda { |f| f[:status] == "home" || f[:status] == "sleeping" }),
541
+
542
+ KBS::Condition.new(:temperature_alert, {
543
+ severity: :severity?
544
+ }),
545
+
546
+ KBS::Condition.new(:energy_alert, {
547
+ type: "high_consumption"
548
+ })
549
+ ]
550
+
551
+ r.action = lambda do |facts, bindings|
552
+ # Allow HVAC to continue despite high energy
553
+ send_message(:hvac_control, {
554
+ action: "maintain_comfort",
555
+ reason: "occupancy_priority"
556
+ }, priority: 85)
557
+ end
558
+ end
559
+
560
+ @engine.add_rule(security_priority_rule)
561
+ @engine.add_rule(comfort_priority_rule)
562
+ end
563
+
564
+ def process
565
+ @engine.run
566
+ end
567
+ end
568
+
569
+ # Multi-agent system coordinator
570
+ class SmartHomeSystem
571
+ attr_reader :engine, :agents
572
+
573
+ def initialize(db_path: 'smart_home.db')
574
+ @engine = KBS::Blackboard::Engine.new(db_path: db_path)
575
+ @agents = []
576
+ @running = false
577
+
578
+ setup_agents
579
+ end
580
+
581
+ def setup_agents
582
+ @agents << TemperatureAgent.new(@engine, target_temp: 22.0)
583
+ @agents << SecurityAgent.new(@engine)
584
+ @agents << EnergyAgent.new(@engine, max_usage: 5000)
585
+ @agents << ScheduleAgent.new(@engine)
586
+ @agents << ArbitrationAgent.new(@engine)
587
+ end
588
+
589
+ def start
590
+ @running = true
591
+ @agents.each(&:start)
592
+
593
+ puts "Smart Home System started with #{@agents.size} agents"
594
+ end
595
+
596
+ def stop
597
+ @running = false
598
+ @agents.each(&:stop)
599
+
600
+ @engine.close
601
+ puts "Smart Home System stopped"
602
+ end
603
+
604
+ def add_sensor_reading(type, attributes)
605
+ @engine.add_fact(:sensor_reading, {
606
+ type: type,
607
+ timestamp: Time.now,
608
+ **attributes
609
+ })
610
+ end
611
+
612
+ def trigger_time_event(event, attributes = {})
613
+ @engine.add_fact(:time_event, {
614
+ event: event,
615
+ timestamp: Time.now,
616
+ **attributes
617
+ })
618
+ end
619
+
620
+ def run_cycle
621
+ # Each agent processes in sequence
622
+ @agents.each(&:run_cycle)
623
+ end
624
+
625
+ def run_continuous(interval: 1)
626
+ while @running
627
+ run_cycle
628
+ sleep interval
629
+ end
630
+ end
631
+
632
+ def status
633
+ {
634
+ running: @running,
635
+ agents: @agents.map { |a| { name: a.name, running: a.running? } },
636
+ facts: @engine.facts.size,
637
+ alerts: @engine.facts.select { |f| f.type.to_s.include?("alert") }.size
638
+ }
639
+ end
640
+ end
641
+
642
+ # Usage Example 1: Temperature Control
643
+ puts "=== Example 1: Temperature Control ==="
644
+ system = SmartHomeSystem.new(db_path: ':memory:')
645
+ system.start
646
+
647
+ # Add temperature reading
648
+ system.add_sensor_reading("temperature", {
649
+ location: "living_room",
650
+ value: 26.0 # Above target (22°C)
651
+ })
652
+
653
+ # Run agent cycle
654
+ system.run_cycle
655
+
656
+ # Check for temperature alerts
657
+ alerts = system.engine.facts.select { |f| f.type == :temperature_alert }
658
+ puts "\nTemperature Alerts:"
659
+ alerts.each do |alert|
660
+ puts " Location: #{alert[:location]}"
661
+ puts " Temperature: #{alert[:temperature]}°C"
662
+ puts " Severity: #{alert[:severity]}"
663
+ end
664
+
665
+ # Check HVAC messages
666
+ hvac_messages = []
667
+ while (msg = system.engine.pop_message(:hvac_control))
668
+ hvac_messages << msg
669
+ end
670
+
671
+ puts "\nHVAC Control Messages:"
672
+ hvac_messages.each do |msg|
673
+ puts " From: #{msg[:content][:from]}"
674
+ puts " Action: #{msg[:content][:content][:action]}"
675
+ puts " Priority: #{msg[:priority]}"
676
+ end
677
+
678
+ # Usage Example 2: Security Event
679
+ puts "\n\n=== Example 2: Security Event ==="
680
+ system2 = SmartHomeSystem.new(db_path: ':memory:')
681
+ system2.start
682
+
683
+ # Set away mode
684
+ system2.engine.add_fact(:occupancy, { status: "away" })
685
+
686
+ # Motion detected while away
687
+ system2.add_sensor_reading("motion", {
688
+ location: "living_room",
689
+ detected: true
690
+ })
691
+
692
+ system2.run_cycle
693
+
694
+ # Check security alerts
695
+ security_alerts = system2.engine.facts.select { |f| f.type == :security_alert }
696
+ puts "\nSecurity Alerts:"
697
+ security_alerts.each do |alert|
698
+ puts " Type: #{alert[:type]}"
699
+ puts " Location: #{alert[:location]}"
700
+ puts " Severity: #{alert[:severity]}"
701
+ end
702
+
703
+ # Check notifications
704
+ notifications = []
705
+ while (msg = system2.engine.pop_message(:notifications))
706
+ notifications << msg
707
+ end
708
+
709
+ puts "\nNotifications:"
710
+ notifications.each do |msg|
711
+ puts " Type: #{msg[:content][:content][:type]}"
712
+ puts " Message: #{msg[:content][:content][:message]}"
713
+ puts " Priority: #{msg[:priority]}"
714
+ end
715
+
716
+ # Usage Example 3: Energy Management with Arbitration
717
+ puts "\n\n=== Example 3: Energy Management ==="
718
+ system3 = SmartHomeSystem.new(db_path: ':memory:')
719
+ system3.start
720
+
721
+ # High energy consumption
722
+ system3.add_sensor_reading("power", { value: 6000 }) # Above 5000W limit
723
+
724
+ # Also high temperature (competing concern)
725
+ system3.add_sensor_reading("temperature", {
726
+ location: "bedroom",
727
+ value: 26.0
728
+ })
729
+
730
+ # Home occupancy
731
+ system3.engine.add_fact(:occupancy, { status: "home" })
732
+
733
+ system3.run_cycle
734
+
735
+ # Check arbitration
736
+ energy_alerts = system3.engine.facts.select { |f| f.type == :energy_alert }
737
+ temp_alerts = system3.engine.facts.select { |f| f.type == :temperature_alert }
738
+
739
+ puts "\nEnergy Alerts: #{energy_alerts.size}"
740
+ puts "Temperature Alerts: #{temp_alerts.size}"
741
+
742
+ # HVAC should maintain comfort despite high energy (arbitration)
743
+ hvac_msgs = []
744
+ while (msg = system3.engine.pop_message(:hvac_control))
745
+ hvac_msgs << msg
746
+ end
747
+
748
+ puts "\nHVAC Messages:"
749
+ hvac_msgs.each do |msg|
750
+ puts " Action: #{msg[:content][:content][:action]}"
751
+ end
752
+
753
+ # Usage Example 4: Morning Routine
754
+ puts "\n\n=== Example 4: Morning Routine ==="
755
+ system4 = SmartHomeSystem.new(db_path: ':memory:')
756
+ system4.start
757
+
758
+ # Trigger morning event
759
+ system4.trigger_time_event("morning", { hour: 7 })
760
+
761
+ system4.run_cycle
762
+
763
+ # Check messages sent to various subsystems
764
+ temp_msgs = []
765
+ while (msg = system4.engine.pop_message(:temperature_control))
766
+ temp_msgs << msg
767
+ end
768
+
769
+ device_msgs = []
770
+ while (msg = system4.engine.pop_message(:device_control))
771
+ device_msgs << msg
772
+ end
773
+
774
+ puts "\nMorning Routine Executed:"
775
+ puts " Temperature control messages: #{temp_msgs.size}"
776
+ puts " Device control messages: #{device_msgs.size}"
777
+
778
+ temp_msgs.each do |msg|
779
+ puts " - Set temperature to #{msg[:content][:content][:temperature]}°C"
780
+ end
781
+
782
+ device_msgs.each do |msg|
783
+ puts " - #{msg[:content][:content][:action]}"
784
+ end
785
+
786
+ puts "\nSystem Status:"
787
+ puts system4.status.inspect
788
+ ```
789
+
790
+ ## Key Features
791
+
792
+ ### 1. Agent Autonomy
793
+
794
+ Each agent operates independently:
795
+
796
+ ```ruby
797
+ class Agent
798
+ def run_cycle
799
+ return unless running?
800
+ process # Agent-specific logic
801
+ end
802
+ end
803
+ ```
804
+
805
+ ### 2. Blackboard Coordination
806
+
807
+ Shared workspace for collaboration:
808
+
809
+ ```ruby
810
+ # Agent 1 writes fact
811
+ @engine.add_fact(:temperature_alert, { location: "bedroom" })
812
+
813
+ # Agent 2 reads fact and responds
814
+ temperature_alert = @engine.facts.find { |f|
815
+ f.type == :temperature_alert && f[:location] == "bedroom"
816
+ }
817
+ ```
818
+
819
+ ### 3. Message Passing
820
+
821
+ Priority-based communication:
822
+
823
+ ```ruby
824
+ # Temperature agent sends message
825
+ send_message(:hvac_control, {
826
+ action: "cool",
827
+ location: "bedroom"
828
+ }, priority: 80)
829
+
830
+ # HVAC controller receives message
831
+ msg = @engine.pop_message(:hvac_control)
832
+ # Process highest priority message first
833
+ ```
834
+
835
+ ### 4. Conflict Resolution
836
+
837
+ Arbitration agent resolves competing goals:
838
+
839
+ ```ruby
840
+ # Security overrides energy savings
841
+ KBS::Rule.new("security_overrides_energy") do |r|
842
+ r.conditions = [
843
+ KBS::Condition.new(:security_alert, { severity: "critical" }),
844
+ KBS::Condition.new(:energy_alert, { type: "high_consumption" })
845
+ ]
846
+
847
+ r.action = lambda do |facts, bindings|
848
+ # Cancel energy reduction
849
+ send_message(:device_control, {
850
+ action: "cancel_energy_reduction",
851
+ reason: "security_priority"
852
+ }, priority: 95)
853
+ end
854
+ end
855
+ ```
856
+
857
+ ### 5. Emergent Behavior
858
+
859
+ Complex system behavior from simple agent rules:
860
+
861
+ ```ruby
862
+ # Temperature agent: "Keep temperature at 22°C"
863
+ # Energy agent: "Don't exceed 5000W"
864
+ # Arbitration agent: "Comfort during occupied hours"
865
+ # Result: System automatically balances comfort and efficiency
866
+ ```
867
+
868
+ ## Multi-Agent Patterns
869
+
870
+ ### Agent Roles
871
+
872
+ **Reactive Agents**: Respond to immediate stimuli
873
+
874
+ ```ruby
875
+ class ReactiveAgent < Agent
876
+ def process
877
+ # React to current sensor readings
878
+ sensor_facts = @engine.facts.select { |f| f.type == :sensor_reading }
879
+ sensor_facts.each { |fact| react_to(fact) }
880
+ end
881
+ end
882
+ ```
883
+
884
+ **Proactive Agents**: Plan and execute goals
885
+
886
+ ```ruby
887
+ class ProactiveAgent < Agent
888
+ def process
889
+ # Check goals
890
+ goals = @engine.facts.select { |f| f.type == :goal }
891
+ goals.each { |goal| plan_for(goal) }
892
+ end
893
+ end
894
+ ```
895
+
896
+ **Social Agents**: Collaborate with other agents
897
+
898
+ ```ruby
899
+ class SocialAgent < Agent
900
+ def process
901
+ # Coordinate with other agents
902
+ messages = receive_messages(:coordination)
903
+ messages.each { |msg| coordinate_with(msg) }
904
+ end
905
+ end
906
+ ```
907
+
908
+ ### Communication Protocols
909
+
910
+ **Request-Reply**:
911
+
912
+ ```ruby
913
+ # Agent A sends request
914
+ send_message(:task_queue, {
915
+ type: "request",
916
+ request_id: SecureRandom.uuid,
917
+ action: "analyze_data"
918
+ }, priority: 50)
919
+
920
+ # Agent B processes and replies
921
+ request = @engine.pop_message(:task_queue)
922
+ result = process_request(request)
923
+
924
+ send_message(:responses, {
925
+ type: "reply",
926
+ request_id: request[:content][:request_id],
927
+ result: result
928
+ }, priority: 60)
929
+ ```
930
+
931
+ **Broadcast**:
932
+
933
+ ```ruby
934
+ # Agent sends to all
935
+ @engine.add_fact(:broadcast, {
936
+ from: @name,
937
+ message: "System shutdown in 5 minutes"
938
+ })
939
+
940
+ # All agents read
941
+ broadcasts = @engine.facts.select { |f|
942
+ f.type == :broadcast && f[:from] != @name
943
+ }
944
+ ```
945
+
946
+ **Negotiation**:
947
+
948
+ ```ruby
949
+ # Agent proposes
950
+ @engine.add_fact(:proposal, {
951
+ from: @name,
952
+ resource: "hvac_bedroom",
953
+ duration: 30,
954
+ priority: 5
955
+ })
956
+
957
+ # Other agents bid or decline
958
+ existing_proposals = @engine.facts.select { |f| f.type == :proposal }
959
+ if can_accept?(existing_proposals)
960
+ @engine.add_fact(:acceptance, { proposal_id: proposal.id })
961
+ else
962
+ @engine.add_fact(:counter_proposal, { ... })
963
+ end
964
+ ```
965
+
966
+ ### Coordination Strategies
967
+
968
+ **Centralized Coordination**:
969
+
970
+ ```ruby
971
+ class CoordinatorAgent < Agent
972
+ def process
973
+ # Collect all agent requests
974
+ requests = []
975
+ @agents.each do |agent|
976
+ while (msg = @engine.pop_message(:"#{agent.name}_requests"))
977
+ requests << msg
978
+ end
979
+ end
980
+
981
+ # Optimize schedule
982
+ schedule = optimize_schedule(requests)
983
+
984
+ # Dispatch commands
985
+ schedule.each do |task|
986
+ send_message(:"#{task[:agent]}_commands", task, priority: 70)
987
+ end
988
+ end
989
+ end
990
+ ```
991
+
992
+ **Distributed Coordination**:
993
+
994
+ ```ruby
995
+ class DistributedAgent < Agent
996
+ def process
997
+ # Each agent coordinates locally
998
+ neighbors = find_neighbors
999
+ neighbors.each do |neighbor|
1000
+ send_message(:"#{neighbor}_coordination", {
1001
+ from: @name,
1002
+ state: current_state
1003
+ }, priority: 50)
1004
+ end
1005
+
1006
+ # Adjust based on neighbor states
1007
+ neighbor_states = receive_messages(:"#{@name}_coordination")
1008
+ adjust_behavior(neighbor_states)
1009
+ end
1010
+ end
1011
+ ```
1012
+
1013
+ **Market-Based Coordination**:
1014
+
1015
+ ```ruby
1016
+ class MarketAgent < Agent
1017
+ def process
1018
+ # Bid on tasks based on cost
1019
+ tasks = @engine.facts.select { |f| f.type == :available_task }
1020
+
1021
+ tasks.each do |task|
1022
+ cost = calculate_cost(task)
1023
+ @engine.add_fact(:bid, {
1024
+ agent: @name,
1025
+ task_id: task.id,
1026
+ cost: cost
1027
+ })
1028
+ end
1029
+
1030
+ # Winner takes task
1031
+ my_bids = @engine.facts.select { |f|
1032
+ f.type == :bid && f[:agent] == @name
1033
+ }
1034
+
1035
+ my_bids.each do |bid|
1036
+ if winning_bid?(bid)
1037
+ execute_task(bid[:task_id])
1038
+ end
1039
+ end
1040
+ end
1041
+ end
1042
+ ```
1043
+
1044
+ ## Advanced Features
1045
+
1046
+ ### Agent Discovery
1047
+
1048
+ ```ruby
1049
+ def discover_agents
1050
+ agent_statuses = @engine.facts.select { |f| f.type == :agent_status }
1051
+ active_agents = agent_statuses.select { |a| a[:status] == "started" }
1052
+
1053
+ active_agents.map { |a| a[:agent] }
1054
+ end
1055
+ ```
1056
+
1057
+ ### Dynamic Agent Creation
1058
+
1059
+ ```ruby
1060
+ def spawn_agent(agent_class, *args)
1061
+ new_agent = agent_class.new(@engine, *args)
1062
+ @agents << new_agent
1063
+ new_agent.start
1064
+ new_agent
1065
+ end
1066
+
1067
+ # Example: Spawn specialist agent
1068
+ if complex_problem_detected?
1069
+ specialist = spawn_agent(SpecialistAgent, problem_type)
1070
+ end
1071
+ ```
1072
+
1073
+ ### Agent Lifecycle Management
1074
+
1075
+ ```ruby
1076
+ class AgentManager
1077
+ def initialize(engine)
1078
+ @engine = engine
1079
+ @agents = {}
1080
+ end
1081
+
1082
+ def register(agent)
1083
+ @agents[agent.name] = agent
1084
+
1085
+ @engine.add_fact(:agent_registered, {
1086
+ name: agent.name,
1087
+ type: agent.class.name,
1088
+ timestamp: Time.now
1089
+ })
1090
+ end
1091
+
1092
+ def deregister(agent_name)
1093
+ agent = @agents.delete(agent_name)
1094
+ agent&.stop
1095
+
1096
+ @engine.add_fact(:agent_deregistered, {
1097
+ name: agent_name,
1098
+ timestamp: Time.now
1099
+ })
1100
+ end
1101
+
1102
+ def monitor_agents
1103
+ @agents.each do |name, agent|
1104
+ unless agent.running?
1105
+ # Restart failed agent
1106
+ agent.start
1107
+ @engine.add_fact(:agent_restarted, {
1108
+ name: name,
1109
+ timestamp: Time.now
1110
+ })
1111
+ end
1112
+ end
1113
+ end
1114
+ end
1115
+ ```
1116
+
1117
+ ### Fault Tolerance
1118
+
1119
+ ```ruby
1120
+ class FaultTolerantAgent < Agent
1121
+ def run_cycle
1122
+ return unless running?
1123
+
1124
+ begin
1125
+ process
1126
+ rescue => e
1127
+ handle_error(e)
1128
+ end
1129
+ end
1130
+
1131
+ def handle_error(error)
1132
+ @engine.add_fact(:agent_error, {
1133
+ agent: @name,
1134
+ error: error.message,
1135
+ timestamp: Time.now
1136
+ })
1137
+
1138
+ send_message(:system_alerts, {
1139
+ type: "agent_failure",
1140
+ agent: @name,
1141
+ error: error.message
1142
+ }, priority: 100)
1143
+
1144
+ # Attempt recovery
1145
+ attempt_recovery
1146
+ end
1147
+
1148
+ def attempt_recovery
1149
+ # Restart agent's subsystems
1150
+ # Or notify manager for replacement
1151
+ end
1152
+ end
1153
+ ```
1154
+
1155
+ ## Testing
1156
+
1157
+ ```ruby
1158
+ require 'minitest/autorun'
1159
+
1160
+ class TestMultiAgentSystem < Minitest::Test
1161
+ def setup
1162
+ @system = SmartHomeSystem.new(db_path: ':memory:')
1163
+ @system.start
1164
+ end
1165
+
1166
+ def teardown
1167
+ @system.stop
1168
+ end
1169
+
1170
+ def test_temperature_agent_creates_alert
1171
+ @system.add_sensor_reading("temperature", {
1172
+ location: "bedroom",
1173
+ value: 26.0
1174
+ })
1175
+
1176
+ @system.run_cycle
1177
+
1178
+ alerts = @system.engine.facts.select { |f| f.type == :temperature_alert }
1179
+
1180
+ assert_equal 1, alerts.size
1181
+ assert_equal "bedroom", alerts.first[:location]
1182
+ assert_equal "high", alerts.first[:severity]
1183
+ end
1184
+
1185
+ def test_security_agent_detects_intrusion
1186
+ @system.engine.add_fact(:occupancy, { status: "away" })
1187
+
1188
+ @system.add_sensor_reading("motion", {
1189
+ location: "living_room",
1190
+ detected: true
1191
+ })
1192
+
1193
+ @system.run_cycle
1194
+
1195
+ alerts = @system.engine.facts.select { |f|
1196
+ f.type == :security_alert && f[:type] == "intrusion"
1197
+ }
1198
+
1199
+ assert_equal 1, alerts.size
1200
+ assert_equal "critical", alerts.first[:severity]
1201
+ end
1202
+
1203
+ def test_energy_agent_triggers_reduction
1204
+ @system.add_sensor_reading("power", { value: 6000 })
1205
+
1206
+ @system.run_cycle
1207
+
1208
+ energy_alerts = @system.engine.facts.select { |f|
1209
+ f.type == :energy_alert
1210
+ }
1211
+
1212
+ assert_equal 1, energy_alerts.size
1213
+
1214
+ # Check for reduction message
1215
+ msg = @system.engine.pop_message(:device_control)
1216
+ assert msg
1217
+ assert_equal "reduce_consumption", msg[:content][:content][:action]
1218
+ end
1219
+
1220
+ def test_arbitration_security_over_energy
1221
+ # Create conflict: security alert + energy alert
1222
+ @system.engine.add_fact(:security_alert, {
1223
+ type: "intrusion",
1224
+ severity: "critical"
1225
+ })
1226
+
1227
+ @system.add_sensor_reading("power", { value: 6000 })
1228
+
1229
+ @system.run_cycle
1230
+
1231
+ # Arbitration should send cancellation
1232
+ msg = @system.engine.pop_message(:device_control)
1233
+ while msg
1234
+ if msg[:content][:content][:action] == "cancel_energy_reduction"
1235
+ assert_equal "security_priority", msg[:content][:content][:reason]
1236
+ return
1237
+ end
1238
+ msg = @system.engine.pop_message(:device_control)
1239
+ end
1240
+
1241
+ flunk "Expected cancellation message not found"
1242
+ end
1243
+
1244
+ def test_schedule_agent_morning_routine
1245
+ @system.trigger_time_event("morning", { hour: 7 })
1246
+
1247
+ @system.run_cycle
1248
+
1249
+ # Check occupancy updated
1250
+ occupancy = @system.engine.facts.find { |f| f.type == :occupancy }
1251
+ assert_equal "home", occupancy[:status]
1252
+
1253
+ # Check routine executed
1254
+ routine = @system.engine.facts.find { |f|
1255
+ f.type == :routine_executed && f[:type] == "morning"
1256
+ }
1257
+ assert routine
1258
+ end
1259
+
1260
+ def test_agent_coordination_via_messages
1261
+ temp_agent = @system.agents.find { |a| a.is_a?(TemperatureAgent) }
1262
+
1263
+ @system.add_sensor_reading("temperature", {
1264
+ location: "bedroom",
1265
+ value: 26.0
1266
+ })
1267
+
1268
+ @system.run_cycle
1269
+
1270
+ # Temperature agent should send HVAC message
1271
+ msg = @system.engine.pop_message(:hvac_control)
1272
+
1273
+ assert msg
1274
+ assert_equal "TemperatureAgent", msg[:content][:from]
1275
+ assert_equal "cool", msg[:content][:content][:action]
1276
+ end
1277
+
1278
+ def test_system_status
1279
+ status = @system.status
1280
+
1281
+ assert status[:running]
1282
+ assert_equal 5, status[:agents].size
1283
+ assert status[:agents].all? { |a| a[:running] }
1284
+ end
1285
+ end
1286
+ ```
1287
+
1288
+ ## Performance Considerations
1289
+
1290
+ ### Use Redis for High-Throughput
1291
+
1292
+ ```ruby
1293
+ require 'kbs/blackboard/persistence/redis_store'
1294
+
1295
+ store = KBS::Blackboard::Persistence::RedisStore.new(
1296
+ url: 'redis://localhost:6379/0'
1297
+ )
1298
+
1299
+ system = SmartHomeSystem.new(store: store)
1300
+ # 100x faster message passing
1301
+ ```
1302
+
1303
+ ### Agent Thread Pools
1304
+
1305
+ ```ruby
1306
+ require 'concurrent'
1307
+
1308
+ class ThreadedSmartHomeSystem < SmartHomeSystem
1309
+ def run_continuous(interval: 1)
1310
+ pool = Concurrent::FixedThreadPool.new(5)
1311
+
1312
+ while @running
1313
+ @agents.each do |agent|
1314
+ pool.post { agent.run_cycle }
1315
+ end
1316
+
1317
+ sleep interval
1318
+ end
1319
+
1320
+ pool.shutdown
1321
+ pool.wait_for_termination
1322
+ end
1323
+ end
1324
+ ```
1325
+
1326
+ ## Next Steps
1327
+
1328
+ - **[Blackboard Memory](../guides/blackboard-memory.md)** - Shared workspace details
1329
+ - **[Performance Guide](../advanced/performance.md)** - Optimize multi-agent systems
1330
+ - **[Testing Guide](../advanced/testing.md)** - Test agent interactions
1331
+ - **[API Reference](../api/blackboard.md)** - Blackboard API
1332
+
1333
+ ---
1334
+
1335
+ *Multi-agent systems enable emergent intelligence through collaboration. Each agent contributes specialized expertise, and the system as a whole solves problems no single agent could solve alone.*