kbs 0.1.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +19 -0
  3. data/README.md +85 -57
  4. data/docs/advanced/performance.md +109 -76
  5. data/docs/advanced/testing.md +399 -263
  6. data/docs/api/blackboard.md +1 -1
  7. data/docs/api/engine.md +77 -8
  8. data/docs/api/facts.md +3 -3
  9. data/docs/api/rules.md +110 -40
  10. data/docs/architecture/blackboard.md +108 -117
  11. data/docs/assets/images/fact-rule-relationship.svg +65 -0
  12. data/docs/assets/images/fact-structure.svg +42 -0
  13. data/docs/assets/images/inference-cycle.svg +47 -0
  14. data/docs/assets/images/kb-components.svg +43 -0
  15. data/docs/assets/images/rule-structure.svg +44 -0
  16. data/docs/assets/images/trading-signal-network.svg +1 -1
  17. data/docs/examples/index.md +219 -5
  18. data/docs/guides/blackboard-memory.md +89 -58
  19. data/docs/guides/dsl.md +24 -24
  20. data/docs/guides/getting-started.md +109 -107
  21. data/docs/guides/writing-rules.md +470 -311
  22. data/docs/index.md +16 -18
  23. data/docs/quick-start.md +92 -99
  24. data/docs/what-is-a-fact.md +694 -0
  25. data/docs/what-is-a-knowledge-base.md +350 -0
  26. data/docs/what-is-a-rule.md +833 -0
  27. data/examples/.gitignore +1 -0
  28. data/examples/advanced_example_dsl.rb +1 -1
  29. data/examples/ai_enhanced_kbs_dsl.rb +1 -1
  30. data/examples/car_diagnostic_dsl.rb +1 -1
  31. data/examples/concurrent_inference_demo.rb +0 -1
  32. data/examples/concurrent_inference_demo_dsl.rb +0 -1
  33. data/examples/csv_trading_system_dsl.rb +1 -1
  34. data/examples/iot_demo_using_dsl.rb +1 -1
  35. data/examples/portfolio_rebalancing_system_dsl.rb +1 -1
  36. data/examples/rule_source_demo.rb +123 -0
  37. data/examples/stock_trading_advanced_dsl.rb +1 -1
  38. data/examples/temp_dsl.txt +6214 -5269
  39. data/examples/timestamped_trading_dsl.rb +1 -1
  40. data/examples/trading_demo_dsl.rb +1 -1
  41. data/examples/working_demo_dsl.rb +1 -1
  42. data/lib/kbs/decompiler.rb +204 -0
  43. data/lib/kbs/dsl/knowledge_base.rb +100 -1
  44. data/lib/kbs/dsl.rb +3 -1
  45. data/lib/kbs/engine.rb +41 -0
  46. data/lib/kbs/version.rb +1 -1
  47. data/lib/kbs.rb +14 -12
  48. data/mkdocs.yml +30 -30
  49. metadata +15 -10
  50. data/docs/DOCUMENTATION_STATUS.md +0 -158
  51. data/docs/examples/expert-systems.md +0 -1031
  52. data/docs/examples/multi-agent.md +0 -1335
  53. data/docs/examples/stock-trading.md +0 -488
  54. data/examples/knowledge_base.db +0 -0
  55. data/examples/temp.txt +0 -7693
data/docs/guides/dsl.md CHANGED
@@ -32,7 +32,7 @@ kb = KBS.knowledge_base do
32
32
 
33
33
  on :temperature, value: greater_than(80), location: :loc?
34
34
 
35
- perform do |bindings|
35
+ perform do |facts, bindings|
36
36
  puts "High temperature at #{bindings[:loc?]}"
37
37
  end
38
38
  end
@@ -314,7 +314,7 @@ rule "rule_name" do
314
314
  on :another_type, field: predicate
315
315
 
316
316
  # Action
317
- perform do |bindings|
317
+ perform do |facts, bindings|
318
318
  # Code to execute
319
319
  end
320
320
  end
@@ -367,7 +367,7 @@ end
367
367
  rule "log_reading" do
368
368
  priority 1 # Low priority
369
369
  on :temperature, value: :temp?
370
- perform { |b| log(b[:temp?]) }
370
+ perform { |facts, b| log(b[:temp?]) }
371
371
  end
372
372
  ```
373
373
 
@@ -436,7 +436,7 @@ Capture attribute values in variables (symbols starting with `?`):
436
436
  on :temperature, value: :temp?, location: :loc?
437
437
 
438
438
  # In action:
439
- perform do |bindings|
439
+ perform do |facts, bindings|
440
440
  puts "Temperature: #{bindings[:temp?]}"
441
441
  puts "Location: #{bindings[:loc?]}"
442
442
  end
@@ -648,7 +648,7 @@ Variables allow you to:
648
648
 
649
649
  ### Variable Syntax
650
650
 
651
- Variables are symbols starting with `?`:
651
+ Variables are symbols ending with `?`:
652
652
 
653
653
  ```ruby
654
654
  :temp? # Variable named "temp"
@@ -664,7 +664,7 @@ Variables are symbols starting with `?`:
664
664
  rule "temperature_report" do
665
665
  on :temperature, value: :temp?, location: :loc?, timestamp: :time?
666
666
 
667
- perform do |bindings|
667
+ perform do |facts, bindings|
668
668
  puts "Temperature at #{bindings[:loc?]}: #{bindings[:temp?]}°F"
669
669
  puts "Recorded: #{bindings[:time?]}"
670
670
  end
@@ -682,7 +682,7 @@ rule "check_inventory" do
682
682
  on :order, product_id: :pid?, quantity: :qty?
683
683
  on :inventory, product_id: :pid?, available: :avail?
684
684
 
685
- perform do |bindings|
685
+ perform do |facts, bindings|
686
686
  if bindings[:avail?] < bindings[:qty?]
687
687
  puts "Insufficient inventory for product #{bindings[:pid?]}"
688
688
  end
@@ -705,7 +705,7 @@ rule "sensor_temperature_correlation" do
705
705
  on :temperature, sensor_id: :sensor_id?, value: :temp?
706
706
  on :reading, sensor_id: :sensor_id?, timestamp: :time?
707
707
 
708
- perform do |bindings|
708
+ perform do |facts, bindings|
709
709
  # All three facts share the same sensor_id
710
710
  puts "Sensor #{bindings[:sensor_id?]} at #{bindings[:loc?]}"
711
711
  puts "Reading: #{bindings[:temp?]}°F at #{bindings[:time?]}"
@@ -777,7 +777,7 @@ rule "no_matching_inventory" do
777
777
  on :order, product_id: :pid?
778
778
  without :inventory, product_id: :pid?
779
779
 
780
- perform do |bindings|
780
+ perform do |facts, bindings|
781
781
  puts "No inventory for product #{bindings[:pid?]}"
782
782
  end
783
783
  end
@@ -810,7 +810,7 @@ end
810
810
  rule "sensor_timeout" do
811
811
  on :sensor, id: :sensor_id?, expected: true
812
812
  without :reading, sensor_id: :sensor_id?
813
- perform { |b| puts "Sensor #{b[:sensor_id?]} timeout" }
813
+ perform { |facts, b| puts "Sensor #{b[:sensor_id?]} timeout" }
814
814
  end
815
815
  ```
816
816
 
@@ -827,7 +827,7 @@ All of these are **aliases**:
827
827
  - **`perform(&block)`** - Primary action keyword
828
828
  - **`action(&block)`** - Alias
829
829
  - **`execute(&block)`** - Alias
830
- - **`then(&block)`** - Alias
830
+ - **`then(&block)`** - Alias - TODO: isn't "then" a ruby keyword?
831
831
 
832
832
  ---
833
833
 
@@ -839,7 +839,7 @@ Actions receive a `bindings` hash containing all variable bindings:
839
839
  rule "example" do
840
840
  on :temperature, value: :temp?, location: :loc?
841
841
 
842
- perform do |bindings|
842
+ perform do |facts, bindings|
843
843
  temp = bindings[:temp?]
844
844
  location = bindings[:loc?]
845
845
  puts "Temperature at #{location}: #{temp}°F"
@@ -855,7 +855,7 @@ Actions can:
855
855
 
856
856
  1. **Read bindings**:
857
857
  ```ruby
858
- perform do |bindings|
858
+ perform do |facts, bindings|
859
859
  value = bindings[:temp?]
860
860
  end
861
861
  ```
@@ -874,7 +874,7 @@ end
874
874
 
875
875
  3. **Call external methods**:
876
876
  ```ruby
877
- perform do |bindings|
877
+ perform do |facts, bindings|
878
878
  send_email_alert(bindings[:temp?])
879
879
  log_to_database(bindings)
880
880
  trigger_alarm if bindings[:level?] == "critical"
@@ -883,7 +883,7 @@ end
883
883
 
884
884
  4. **Add/remove facts**:
885
885
  ```ruby
886
- perform do |bindings|
886
+ perform do |facts, bindings|
887
887
  # Add derived fact
888
888
  fact :alert, level: "high", source: bindings[:sensor_id?]
889
889
 
@@ -901,7 +901,7 @@ end
901
901
  # Simple logging
902
902
  rule "log_temperature" do
903
903
  on :temperature, value: :temp?
904
- perform { |b| puts "Temperature: #{b[:temp?]}" }
904
+ perform { |facts, b| puts "Temperature: #{b[:temp?]}" }
905
905
  end
906
906
 
907
907
  # State machine transition
@@ -909,7 +909,7 @@ rule "pending_to_processing" do
909
909
  on :order, id: :order_id?, status: "pending"
910
910
  on :worker, status: "available", id: :worker_id?
911
911
 
912
- perform do |bindings|
912
+ perform do |facts, bindings|
913
913
  # Update order status
914
914
  order = query(:order, id: bindings[:order_id?]).first
915
915
  retract order
@@ -1079,7 +1079,7 @@ end
1079
1079
  kb = KBS.knowledge_base do
1080
1080
  rule "example" do
1081
1081
  on :temperature, value: :temp?
1082
- perform { |b| puts b[:temp?] }
1082
+ perform { |facts, b| puts b[:temp?] }
1083
1083
  end
1084
1084
  end
1085
1085
 
@@ -1112,7 +1112,7 @@ kb = KBS.knowledge_base do
1112
1112
  on :temperature, sensor_id: :sensor_id?, value: greater_than(80)
1113
1113
  without :alert, sensor_id: :sensor_id? # No existing alert
1114
1114
 
1115
- perform do |bindings|
1115
+ perform do |facts, bindings|
1116
1116
  puts "⚠️ HIGH TEMPERATURE ALERT"
1117
1117
  puts "Sensor: #{bindings[:sensor_id?]}"
1118
1118
  puts "Temperature: #{bindings[:value?]}°F"
@@ -1132,7 +1132,7 @@ kb = KBS.knowledge_base do
1132
1132
  on :temperature, sensor_id: :sensor_id?, value: less_than(75)
1133
1133
  on :alert, sensor_id: :sensor_id?
1134
1134
 
1135
- perform do |bindings|
1135
+ perform do |facts, bindings|
1136
1136
  puts "✓ Temperature normal for sensor #{bindings[:sensor_id?]}"
1137
1137
 
1138
1138
  # Remove alert
@@ -1167,7 +1167,7 @@ kb = KBS.knowledge_base do
1167
1167
  on :order, id: :order_id?, status: "new", product_id: :pid?, quantity: :qty?
1168
1168
  on :inventory, product_id: :pid?, quantity: :available?
1169
1169
 
1170
- perform do |bindings|
1170
+ perform do |facts, bindings|
1171
1171
  if bindings[:available?] >= bindings[:qty?]
1172
1172
  order = query(:order, id: bindings[:order_id?]).first
1173
1173
  retract order
@@ -1191,7 +1191,7 @@ kb = KBS.knowledge_base do
1191
1191
  product_id: :pid?, quantity: :qty?
1192
1192
  on :inventory, product_id: :pid?, quantity: :available?
1193
1193
 
1194
- perform do |bindings|
1194
+ perform do |facts, bindings|
1195
1195
  # Deduct inventory
1196
1196
  inventory = query(:inventory, product_id: bindings[:pid?]).first
1197
1197
  retract inventory
@@ -1296,13 +1296,13 @@ on :order, status: ->(s) { ["pending", "processing"].include?(s) }
1296
1296
  # Good - Simple, focused action
1297
1297
  rule "log_temperature" do
1298
1298
  on :temperature, value: :temp?
1299
- perform { |b| logger.info("Temperature: #{b[:temp?]}") }
1299
+ perform { |facts, b| logger.info("Temperature: #{b[:temp?]}") }
1300
1300
  end
1301
1301
 
1302
1302
  # Avoid - Complex logic in action
1303
1303
  rule "complex_action" do
1304
1304
  on :temperature, value: :temp?
1305
- perform do |b|
1305
+ perform do |facts, b|
1306
1306
  # 100 lines of complex logic...
1307
1307
  # Better to extract to methods
1308
1308
  end
@@ -28,35 +28,35 @@ gem install kbs
28
28
 
29
29
  Let's create a simple rule that fires when temperature exceeds a threshold.
30
30
 
31
- ### Step 1: Create the Engine
31
+ ### Step 1: Create a Knowledge Base
32
32
 
33
33
  ```ruby
34
34
  require 'kbs'
35
35
 
36
- # Create the inference engine
37
- engine = KBS::Engine.new
36
+ # Create a knowledge base with DSL
37
+ kb = KBS.knowledge_base do
38
+ # Rules will be defined here
39
+ end
38
40
  ```
39
41
 
40
- The engine manages rules, facts, and executes the pattern matching algorithm.
42
+ The knowledge base manages rules, facts, and executes the pattern matching algorithm.
41
43
 
42
44
  ### Step 2: Define a Rule
43
45
 
44
46
  ```ruby
45
- # Define a rule for high temperature alerts
46
- high_temp_rule = KBS::Rule.new("high_temperature_alert") do |r|
47
- r.conditions = [
48
- KBS::Condition.new(:sensor, { id: :sensor_id?, temp: :temp? }),
49
- KBS::Condition.new(:threshold, { id: :sensor_id?, max: :max? })
50
- ]
51
-
52
- r.action = lambda do |facts, bindings|
53
- if bindings[:temp?] > bindings[:max?]
54
- puts "🚨 ALERT: Sensor #{bindings[:sensor_id?]} at #{bindings[:temp?]}°C"
47
+ kb = KBS.knowledge_base do
48
+ # Define a rule for high temperature alerts
49
+ rule "high_temperature_alert" do
50
+ on :sensor, id: :sensor_id?, temp: :temp?
51
+ on :threshold, id: :sensor_id?, max: :max?
52
+
53
+ perform do |facts, bindings|
54
+ if bindings[:temp?] > bindings[:max?]
55
+ puts "🚨 ALERT: Sensor #{bindings[:sensor_id?]} at #{bindings[:temp?]}°C"
56
+ end
55
57
  end
56
58
  end
57
59
  end
58
-
59
- engine.add_rule(high_temp_rule)
60
60
  ```
61
61
 
62
62
  **What this rule does:**
@@ -67,24 +67,32 @@ engine.add_rule(high_temp_rule)
67
67
 
68
68
  **Variable binding** (`:sensor_id?`) ensures we only compare sensors with their own thresholds.
69
69
 
70
- ### Step 3: Add Facts
70
+ ### Step 3: Add Facts and Run
71
71
 
72
72
  ```ruby
73
- # Add sensor reading
74
- engine.add_fact(:sensor, id: "bedroom", temp: 28)
75
-
76
- # Add threshold
77
- engine.add_fact(:threshold, id: "bedroom", max: 25)
78
- ```
79
-
80
- Facts are observations about the world. The engine automatically matches them against rule conditions.
73
+ kb = KBS.knowledge_base do
74
+ rule "high_temperature_alert" do
75
+ on :sensor, id: :sensor_id?, temp: :temp?
76
+ on :threshold, id: :sensor_id?, max: :max?
77
+
78
+ perform do |facts, bindings|
79
+ if bindings[:temp?] > bindings[:max?]
80
+ puts "🚨 ALERT: Sensor #{bindings[:sensor_id?]} at #{bindings[:temp?]}°C"
81
+ end
82
+ end
83
+ end
81
84
 
82
- ### Step 4: Run the Engine
85
+ # Add facts
86
+ fact :sensor, id: "bedroom", temp: 28
87
+ fact :threshold, id: "bedroom", max: 25
83
88
 
84
- ```ruby
85
- engine.run
89
+ # Run inference
90
+ run
91
+ end
86
92
  ```
87
93
 
94
+ Facts are observations about the world. The knowledge base automatically matches them against rule conditions.
95
+
88
96
  **Output:**
89
97
  ```
90
98
  🚨 ALERT: Sensor bedroom at 28°C
@@ -97,10 +105,10 @@ The rule fired because the bedroom temperature (28°C) exceeds its threshold (25
97
105
  Variable binding connects facts across conditions. Here's how it works:
98
106
 
99
107
  ```ruby
100
- r.conditions = [
101
- KBS::Condition.new(:sensor, { id: :sensor_id?, temp: :temp? }),
102
- KBS::Condition.new(:threshold, { id: :sensor_id?, max: :max? })
103
- ]
108
+ rule "example" do
109
+ on :sensor, id: :sensor_id?, temp: :temp?
110
+ on :threshold, id: :sensor_id?, max: :max?
111
+ end
104
112
  ```
105
113
 
106
114
  **Binding Process:**
@@ -119,19 +127,19 @@ Without variable binding, the rule would incorrectly match bedroom sensors with
119
127
  Let's prevent the same alert from firing repeatedly:
120
128
 
121
129
  ```ruby
122
- smart_alert_rule = KBS::Rule.new("smart_temperature_alert") do |r|
123
- r.conditions = [
124
- KBS::Condition.new(:sensor, { id: :sensor_id?, temp: :temp? }),
125
- KBS::Condition.new(:threshold, { id: :sensor_id?, max: :max? }),
130
+ kb = KBS.knowledge_base do
131
+ rule "smart_temperature_alert" do
132
+ on :sensor, id: :sensor_id?, temp: :temp?
133
+ on :threshold, id: :sensor_id?, max: :max?
126
134
  # Only fire if no alert already exists for this sensor
127
- KBS::Condition.new(:alert, { sensor_id: :sensor_id? }, negated: true)
128
- ]
129
-
130
- r.action = lambda do |facts, bindings|
131
- if bindings[:temp?] > bindings[:max?]
132
- puts "🚨 ALERT: Sensor #{bindings[:sensor_id?]} at #{bindings[:temp?]}°C"
133
- # Record that we sent this alert
134
- engine.add_fact(:alert, sensor_id: bindings[:sensor_id?])
135
+ without :alert, sensor_id: :sensor_id?
136
+
137
+ perform do |facts, bindings|
138
+ if bindings[:temp?] > bindings[:max?]
139
+ puts "🚨 ALERT: Sensor #{bindings[:sensor_id?]} at #{bindings[:temp?]}°C"
140
+ # Record that we sent this alert
141
+ fact :alert, sensor_id: bindings[:sensor_id?]
142
+ end
135
143
  end
136
144
  end
137
145
  end
@@ -187,25 +195,24 @@ Learn more: [Blackboard Memory Guide](blackboard-memory.md)
187
195
  When multiple rules match, control firing order with priorities:
188
196
 
189
197
  ```ruby
190
- critical_rule = KBS::Rule.new("critical_alert", priority: 100) do |r|
191
- r.conditions = [
192
- KBS::Condition.new(:sensor, { temp: :temp? })
193
- ]
194
-
195
- r.action = lambda do |facts, bindings|
196
- if bindings[:temp?] > 50
197
- puts "🔥 CRITICAL: Immediate shutdown required!"
198
- exit(1)
198
+ kb = KBS.knowledge_base do
199
+ rule "critical_alert", priority: 100 do
200
+ on :sensor, temp: :temp?
201
+
202
+ perform do |facts, bindings|
203
+ if bindings[:temp?] > 50
204
+ puts "🔥 CRITICAL: Immediate shutdown required!"
205
+ exit(1)
206
+ end
199
207
  end
200
208
  end
201
- end
202
209
 
203
- normal_rule = KBS::Rule.new("normal_alert", priority: 10) do |r|
204
- # ... (less urgent alerts)
210
+ rule "normal_alert", priority: 10 do
211
+ on :sensor, temp: :temp?
212
+ # ... (less urgent alerts)
213
+ perform { |facts| puts "Normal alert" }
214
+ end
205
215
  end
206
-
207
- engine.add_rule(critical_rule)
208
- engine.add_rule(normal_rule)
209
216
  ```
210
217
 
211
218
  **Priority:** Higher numbers fire first. Default is `0`.
@@ -224,74 +231,69 @@ require 'kbs'
224
231
  class TemperatureMonitor
225
232
  def initialize
226
233
  @engine = KBS::Blackboard::Engine.new(db_path: 'sensors.db')
227
- setup_rules
234
+ @kb = setup_rules
228
235
  end
229
236
 
230
237
  def setup_rules
231
- # Rule 1: Send alert when temp exceeds threshold
232
- alert_rule = KBS::Rule.new("temperature_alert", priority: 50) do |r|
233
- r.conditions = [
234
- KBS::Condition.new(:sensor, { id: :id?, temp: :temp? }),
235
- KBS::Condition.new(:threshold, { id: :id?, max: :max? }),
236
- KBS::Condition.new(:alert, { sensor_id: :id? }, negated: true)
237
- ]
238
-
239
- r.action = lambda do |facts, bindings|
240
- if bindings[:temp?] > bindings[:max?]
241
- send_alert(bindings[:id?], bindings[:temp?], bindings[:max?])
242
- @engine.add_fact(:alert, sensor_id: bindings[:id?])
238
+ engine = @engine
239
+ monitor = self
240
+
241
+ KBS.knowledge_base(engine: engine) do
242
+ # Rule 1: Send alert when temp exceeds threshold
243
+ rule "temperature_alert", priority: 50 do
244
+ on :sensor, id: :id?, temp: :temp?
245
+ on :threshold, id: :id?, max: :max?
246
+ without :alert, sensor_id: :id?
247
+
248
+ perform do |facts, bindings|
249
+ if bindings[:temp?] > bindings[:max?]
250
+ monitor.send_alert(bindings[:id?], bindings[:temp?], bindings[:max?])
251
+ fact :alert, sensor_id: bindings[:id?]
252
+ end
243
253
  end
244
254
  end
245
- end
246
255
 
247
- # Rule 2: Clear alert when temp drops below threshold
248
- clear_rule = KBS::Rule.new("clear_alert", priority: 40) do |r|
249
- r.conditions = [
250
- KBS::Condition.new(:sensor, { id: :id?, temp: :temp? }),
251
- KBS::Condition.new(:threshold, { id: :id?, max: :max? }),
252
- KBS::Condition.new(:alert, { sensor_id: :id? })
253
- ]
254
-
255
- r.action = lambda do |facts, bindings|
256
- if bindings[:temp?] <= bindings[:max?]
257
- clear_alert(bindings[:id?])
258
- # Remove the alert fact
259
- alert_fact = facts.find { |f| f.type == :alert && f[:sensor_id] == bindings[:id?] }
260
- @engine.remove_fact(alert_fact) if alert_fact
256
+ # Rule 2: Clear alert when temp drops below threshold
257
+ rule "clear_alert", priority: 40 do
258
+ on :sensor, id: :id?, temp: :temp?
259
+ on :threshold, id: :id?, max: :max?
260
+ on :alert, sensor_id: :id?
261
+
262
+ perform do |facts, bindings|
263
+ if bindings[:temp?] <= bindings[:max?]
264
+ monitor.clear_alert(bindings[:id?])
265
+ # Find and retract the alert fact
266
+ alert_fact = query(:alert, sensor_id: bindings[:id?]).first
267
+ retract alert_fact if alert_fact
268
+ end
261
269
  end
262
270
  end
263
- end
264
271
 
265
- # Rule 3: Emergency shutdown for extreme temps
266
- emergency_rule = KBS::Rule.new("emergency_shutdown", priority: 100) do |r|
267
- r.conditions = [
268
- KBS::Condition.new(:sensor, { temp: :temp? })
269
- ]
272
+ # Rule 3: Emergency shutdown for extreme temps
273
+ rule "emergency_shutdown", priority: 100 do
274
+ on :sensor, temp: :temp?
270
275
 
271
- r.action = lambda do |facts, bindings|
272
- if bindings[:temp?] > 60
273
- emergency_shutdown(bindings[:temp?])
276
+ perform do |facts, bindings|
277
+ if bindings[:temp?] > 60
278
+ monitor.emergency_shutdown(bindings[:temp?])
279
+ end
274
280
  end
275
281
  end
276
282
  end
277
-
278
- @engine.add_rule(alert_rule)
279
- @engine.add_rule(clear_rule)
280
- @engine.add_rule(emergency_rule)
281
283
  end
282
284
 
283
285
  def add_sensor(id, max_temp)
284
- @engine.add_fact(:threshold, id: id, max: max_temp)
286
+ @kb.fact :threshold, id: id, max: max_temp
285
287
  end
286
288
 
287
289
  def update_reading(id, temp)
288
- # Remove old reading
289
- old = @engine.facts.find { |f| f.type == :sensor && f[:id] == id }
290
- @engine.remove_fact(old) if old
290
+ # Find and remove old reading
291
+ old = @kb.query(:sensor, id: id).first
292
+ @kb.retract old if old
291
293
 
292
294
  # Add new reading
293
- @engine.add_fact(:sensor, id: id, temp: temp)
294
- @engine.run
295
+ @kb.fact :sensor, id: id, temp: temp
296
+ @kb.run
295
297
  end
296
298
 
297
299
  def send_alert(sensor_id, temp, threshold)
@@ -375,7 +377,7 @@ Now that you understand the basics, explore:
375
377
  - **[Writing Rules](writing-rules.md)** - Advanced rule patterns and techniques
376
378
  - **[Pattern Matching](pattern-matching.md)** - Deep dive into condition syntax
377
379
  - **[Blackboard Memory](blackboard-memory.md)** - Multi-agent collaboration
378
- - **[Stock Trading Example](../examples/stock-trading.md)** - Real-world application
380
+ - **[Stock Trading Examples](../examples/index.md#stock-trading-systems)** - Real-world applications
379
381
  - **[API Reference](../api/engine.md)** - Complete method documentation
380
382
 
381
383
  ---