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,1321 @@
1
+ # DSL Reference Guide
2
+
3
+ Complete reference for the KBS Domain-Specific Language for defining knowledge bases and rules.
4
+
5
+ ## Table of Contents
6
+
7
+ - [Quick Start](#quick-start)
8
+ - [Knowledge Base](#knowledge-base)
9
+ - [Rule Definition](#rule-definition)
10
+ - [Condition Syntax](#condition-syntax)
11
+ - [Pattern Helpers](#pattern-helpers)
12
+ - [Variable Binding](#variable-binding)
13
+ - [Negation](#negation)
14
+ - [Actions](#actions)
15
+ - [Working with Facts](#working-with-facts)
16
+ - [Introspection](#introspection)
17
+
18
+ ---
19
+
20
+ ## Quick Start
21
+
22
+ The KBS DSL provides a natural, English-like syntax for defining knowledge-based systems:
23
+
24
+ ```ruby
25
+ require 'kbs'
26
+
27
+ kb = KBS.knowledge_base do
28
+ # Define a rule
29
+ rule "high_temperature_alert" do
30
+ desc "Alert when temperature exceeds threshold"
31
+ priority 10
32
+
33
+ on :temperature, value: greater_than(80), location: :loc?
34
+
35
+ perform do |bindings|
36
+ puts "High temperature at #{bindings[:loc?]}"
37
+ end
38
+ end
39
+
40
+ # Add facts
41
+ fact :temperature, value: 85, location: "server_room"
42
+
43
+ # Execute rules
44
+ run
45
+ end
46
+ ```
47
+
48
+ ---
49
+
50
+ ## Knowledge Base
51
+
52
+ ### Creating a Knowledge Base
53
+
54
+ #### `KBS.knowledge_base(&block)`
55
+
56
+ Creates a new knowledge base and evaluates the block in its context.
57
+
58
+ **Returns**: `KBS::DSL::KnowledgeBase` instance
59
+
60
+ **Example**:
61
+ ```ruby
62
+ kb = KBS.knowledge_base do
63
+ # Define rules and add facts here
64
+ end
65
+
66
+ # Access the underlying engine
67
+ kb.engine # => KBS::Engine
68
+
69
+ # Access defined rules
70
+ kb.rules # => Hash of rule_name => KBS::Rule
71
+ ```
72
+
73
+ ---
74
+
75
+ ### Knowledge Base Methods
76
+
77
+ #### `rule(name, &block)` / `defrule(name, &block)`
78
+
79
+ Defines a new rule.
80
+
81
+ **Parameters**:
82
+ - `name` (String or Symbol) - Rule name
83
+ - `&block` - Block containing rule definition
84
+
85
+ **Returns**: `KBS::DSL::RuleBuilder`
86
+
87
+ **Example**:
88
+ ```ruby
89
+ kb = KBS.knowledge_base do
90
+ rule "example_rule" do
91
+ # Rule definition here
92
+ end
93
+
94
+ # Alias
95
+ defrule "another_rule" do
96
+ # Rule definition here
97
+ end
98
+ end
99
+ ```
100
+
101
+ ---
102
+
103
+ #### `fact(type, attributes = {})` / `assert(type, attributes = {})`
104
+
105
+ Adds a fact to working memory.
106
+
107
+ **Parameters**:
108
+ - `type` (Symbol) - Fact type
109
+ - `attributes` (Hash) - Fact attributes
110
+
111
+ **Returns**: `KBS::Fact`
112
+
113
+ **Example**:
114
+ ```ruby
115
+ kb = KBS.knowledge_base do
116
+ fact :temperature, value: 85, location: "server_room"
117
+
118
+ # Alias
119
+ assert :sensor, id: 1, status: "active"
120
+ end
121
+ ```
122
+
123
+ ---
124
+
125
+ #### `retract(fact)`
126
+
127
+ Removes a fact from working memory.
128
+
129
+ **Parameters**:
130
+ - `fact` (KBS::Fact) - Fact to remove
131
+
132
+ **Returns**: `nil`
133
+
134
+ **Example**:
135
+ ```ruby
136
+ kb = KBS.knowledge_base do
137
+ temp_fact = fact :temperature, value: 85
138
+
139
+ # Later...
140
+ retract temp_fact
141
+ end
142
+ ```
143
+
144
+ ---
145
+
146
+ #### `run()`
147
+
148
+ Executes all activated rules.
149
+
150
+ **Returns**: `nil`
151
+
152
+ **Example**:
153
+ ```ruby
154
+ kb = KBS.knowledge_base do
155
+ rule "my_rule" do
156
+ on :temperature, value: greater_than(80)
157
+ perform { puts "High temperature!" }
158
+ end
159
+
160
+ fact :temperature, value: 85
161
+
162
+ run # Fires "my_rule"
163
+ end
164
+ ```
165
+
166
+ ---
167
+
168
+ #### `reset()`
169
+
170
+ Clears all facts from working memory.
171
+
172
+ **Returns**: `nil`
173
+
174
+ **Example**:
175
+ ```ruby
176
+ kb = KBS.knowledge_base do
177
+ fact :temperature, value: 85
178
+ fact :humidity, value: 60
179
+
180
+ reset # All facts removed
181
+
182
+ puts facts.size # => 0
183
+ end
184
+ ```
185
+
186
+ ---
187
+
188
+ #### `facts()`
189
+
190
+ Returns all facts in working memory.
191
+
192
+ **Returns**: `Array<KBS::Fact>`
193
+
194
+ **Example**:
195
+ ```ruby
196
+ kb = KBS.knowledge_base do
197
+ fact :temperature, value: 85
198
+ fact :humidity, value: 60
199
+
200
+ puts facts.size # => 2
201
+
202
+ facts.each do |f|
203
+ puts "#{f.type}: #{f.attributes}"
204
+ end
205
+ end
206
+ ```
207
+
208
+ ---
209
+
210
+ #### `query(type, pattern = {})`
211
+
212
+ Queries facts by type and attributes.
213
+
214
+ **Parameters**:
215
+ - `type` (Symbol) - Fact type to match
216
+ - `pattern` (Hash) - Attribute key-value pairs to match
217
+
218
+ **Returns**: `Array<KBS::Fact>`
219
+
220
+ **Example**:
221
+ ```ruby
222
+ kb = KBS.knowledge_base do
223
+ fact :temperature, value: 85, location: "server_room"
224
+ fact :temperature, value: 75, location: "lobby"
225
+ fact :humidity, value: 60, location: "server_room"
226
+
227
+ # Find all temperature facts
228
+ temps = query(:temperature)
229
+ puts temps.size # => 2
230
+
231
+ # Find temperature facts in server_room
232
+ server_temps = query(:temperature, location: "server_room")
233
+ puts server_temps.size # => 1
234
+ puts server_temps.first[:value] # => 85
235
+ end
236
+ ```
237
+
238
+ ---
239
+
240
+ #### `print_facts()`
241
+
242
+ Displays all facts in working memory.
243
+
244
+ **Returns**: `nil`
245
+
246
+ **Example**:
247
+ ```ruby
248
+ kb = KBS.knowledge_base do
249
+ fact :temperature, value: 85
250
+ fact :humidity, value: 60
251
+
252
+ print_facts
253
+ end
254
+
255
+ # Output:
256
+ # Working Memory Contents:
257
+ # ----------------------------------------
258
+ # 1. temperature(value: 85)
259
+ # 2. humidity(value: 60)
260
+ # ----------------------------------------
261
+ ```
262
+
263
+ ---
264
+
265
+ #### `print_rules()`
266
+
267
+ Displays all defined rules with their conditions.
268
+
269
+ **Returns**: `nil`
270
+
271
+ **Example**:
272
+ ```ruby
273
+ kb = KBS.knowledge_base do
274
+ rule "high_temp" do
275
+ desc "Alert on high temperature"
276
+ priority 10
277
+ on :temperature, value: greater_than(80)
278
+ perform { puts "High temp!" }
279
+ end
280
+
281
+ print_rules
282
+ end
283
+
284
+ # Output:
285
+ # Knowledge Base Rules:
286
+ # ----------------------------------------
287
+ # Rule: high_temp
288
+ # Description: Alert on high temperature
289
+ # Priority: 10
290
+ # Conditions: 1
291
+ # 1. temperature({:value=>#<Proc:...>})
292
+ # ----------------------------------------
293
+ ```
294
+
295
+ ---
296
+
297
+ ## Rule Definition
298
+
299
+ Rules are defined using the `rule` method with a block containing:
300
+
301
+ 1. **Metadata**: Description and priority
302
+ 2. **Conditions**: Patterns to match facts
303
+ 3. **Action**: Code to execute when all conditions match
304
+
305
+ ### Rule Structure
306
+
307
+ ```ruby
308
+ rule "rule_name" do
309
+ desc "Optional description"
310
+ priority 10 # Optional, default: 0
311
+
312
+ # Conditions (one or more)
313
+ on :fact_type, attribute: value, other: :variable?
314
+ on :another_type, field: predicate
315
+
316
+ # Action
317
+ perform do |bindings|
318
+ # Code to execute
319
+ end
320
+ end
321
+ ```
322
+
323
+ ---
324
+
325
+ ### Rule Metadata
326
+
327
+ #### `desc(description)`
328
+
329
+ Sets the rule description (for documentation and debugging).
330
+
331
+ **Parameters**:
332
+ - `description` (String) - Human-readable description
333
+
334
+ **Returns**: `self` (chainable)
335
+
336
+ **Example**:
337
+ ```ruby
338
+ rule "temperature_alert" do
339
+ desc "Alerts when server room temperature exceeds safe threshold"
340
+
341
+ on :temperature, location: "server_room", value: greater_than(80)
342
+ perform { puts "High temperature alert!" }
343
+ end
344
+ ```
345
+
346
+ ---
347
+
348
+ #### `priority(level)`
349
+
350
+ Sets the rule priority (higher priority rules fire first).
351
+
352
+ **Parameters**:
353
+ - `level` (Integer) - Priority level (default: 0)
354
+
355
+ **Returns**: `self` (chainable)
356
+
357
+ **Note**: Priority only affects execution order in `KBS::Blackboard::Engine`, not `KBS::Engine`.
358
+
359
+ **Example**:
360
+ ```ruby
361
+ rule "critical_shutdown" do
362
+ priority 1000 # Highest priority
363
+ on :temperature, value: greater_than(120)
364
+ perform { shutdown_system! }
365
+ end
366
+
367
+ rule "log_reading" do
368
+ priority 1 # Low priority
369
+ on :temperature, value: :temp?
370
+ perform { |b| log(b[:temp?]) }
371
+ end
372
+ ```
373
+
374
+ ---
375
+
376
+ ## Condition Syntax
377
+
378
+ Conditions specify patterns that must match facts in working memory.
379
+
380
+ ### Condition Keywords
381
+
382
+ All of these are **aliases** - use whichever reads best for your domain:
383
+
384
+ - **`on(type, pattern = {}, &block)`** - Primary keyword
385
+ - **`given(type, pattern = {})`** - Alias for `on`
386
+ - **`matches(type, pattern = {})`** - Alias for `on`
387
+ - **`fact(type, pattern = {})`** - Alias for `on`
388
+ - **`exists(type, pattern = {})`** - Alias for `on`
389
+
390
+ **Parameters**:
391
+ - `type` (Symbol) - Fact type to match
392
+ - `pattern` (Hash) - Attribute constraints (key-value pairs)
393
+ - `&block` (optional) - Block-style pattern definition
394
+
395
+ **Returns**: `self` (chainable)
396
+
397
+ ---
398
+
399
+ ### Basic Condition Examples
400
+
401
+ ```ruby
402
+ # Match any temperature fact
403
+ on :temperature
404
+
405
+ # Match temperature with specific value
406
+ on :temperature, value: 85
407
+
408
+ # Match temperature with multiple attributes
409
+ on :temperature, value: 85, location: "server_room"
410
+
411
+ # Using aliases
412
+ given :sensor, status: "active"
413
+ matches :order, status: "pending"
414
+ fact :inventory, quantity: 0
415
+ exists :alert, level: "critical"
416
+ ```
417
+
418
+ ---
419
+
420
+ ### Literal Matching
421
+
422
+ Match exact attribute values:
423
+
424
+ ```ruby
425
+ on :temperature, location: "server_room" # location must equal "server_room"
426
+ on :order, status: "pending", total: 100 # Both must match exactly
427
+ ```
428
+
429
+ ---
430
+
431
+ ### Variable Binding
432
+
433
+ Capture attribute values in variables (symbols starting with `?`):
434
+
435
+ ```ruby
436
+ on :temperature, value: :temp?, location: :loc?
437
+
438
+ # In action:
439
+ perform do |bindings|
440
+ puts "Temperature: #{bindings[:temp?]}"
441
+ puts "Location: #{bindings[:loc?]}"
442
+ end
443
+ ```
444
+
445
+ **Join Test**: Same variable in multiple conditions creates a join:
446
+
447
+ ```ruby
448
+ on :order, product_id: :pid?, quantity: :qty?
449
+ on :inventory, product_id: :pid?, available: :avail?
450
+
451
+ # These conditions only match when product_id is the same in both facts
452
+ ```
453
+
454
+ ---
455
+
456
+ ### Predicate Matching
457
+
458
+ Use lambdas or helper methods for complex conditions:
459
+
460
+ ```ruby
461
+ # Lambda predicate
462
+ on :temperature, value: ->(v) { v > 80 && v < 100 }
463
+
464
+ # Helper method (see Pattern Helpers section)
465
+ on :temperature, value: greater_than(80)
466
+ on :order, total: between(100, 1000)
467
+ on :status, code: one_of("pending", "processing", "completed")
468
+ ```
469
+
470
+ ---
471
+
472
+ ### Block-Style Patterns
473
+
474
+ Define patterns using a block with method-missing magic:
475
+
476
+ ```ruby
477
+ on :temperature do
478
+ value > 80 # Creates lambda: ->(v) { v > 80 }
479
+ location :loc? # Binds variable
480
+ sensor_id 42 # Literal match
481
+ end
482
+
483
+ # Equivalent to:
484
+ on :temperature,
485
+ value: ->(v) { v > 80 },
486
+ location: :loc?,
487
+ sensor_id: 42
488
+ ```
489
+
490
+ **Available operators in blocks**:
491
+ - `>`, `<`, `>=`, `<=` - Comparison operators
492
+ - `==` - Equality (same as literal value)
493
+ - `!=` - Inequality
494
+ - `between(min, max)` - Range check
495
+ - `in(collection)` - Membership check
496
+ - `matches(pattern)` - Regex match
497
+ - `any(*values)` - Match any of the values
498
+ - `all(*conditions)` - All conditions must be true
499
+
500
+ **Example**:
501
+ ```ruby
502
+ on :order do
503
+ total > 1000
504
+ status in ["pending", "processing"]
505
+ customer_email matches(/@example\.com$/)
506
+ priority any(1, 2, 3)
507
+ end
508
+ ```
509
+
510
+ ---
511
+
512
+ ## Pattern Helpers
513
+
514
+ Helper methods available in rule conditions (from `ConditionHelpers` module).
515
+
516
+ ### Comparison Helpers
517
+
518
+ #### `greater_than(value)`
519
+
520
+ Matches values greater than the specified value.
521
+
522
+ **Example**:
523
+ ```ruby
524
+ on :temperature, value: greater_than(80)
525
+ # Equivalent to: value: ->(v) { v > 80 }
526
+ ```
527
+
528
+ ---
529
+
530
+ #### `less_than(value)`
531
+
532
+ Matches values less than the specified value.
533
+
534
+ **Example**:
535
+ ```ruby
536
+ on :inventory, quantity: less_than(10)
537
+ # Equivalent to: quantity: ->(q) { q < 10 }
538
+ ```
539
+
540
+ ---
541
+
542
+ #### `equals(value)`
543
+
544
+ Explicitly matches an exact value (same as literal).
545
+
546
+ **Example**:
547
+ ```ruby
548
+ on :sensor, status: equals("active")
549
+ # Equivalent to: status: "active"
550
+ ```
551
+
552
+ ---
553
+
554
+ #### `not_equal(value)`
555
+
556
+ Matches values not equal to the specified value.
557
+
558
+ **Example**:
559
+ ```ruby
560
+ on :order, status: not_equal("cancelled")
561
+ # Equivalent to: status: ->(s) { s != "cancelled" }
562
+ ```
563
+
564
+ ---
565
+
566
+ ### Range Helpers
567
+
568
+ #### `between(min, max)` / `range(min, max)`
569
+
570
+ Matches values in an inclusive range.
571
+
572
+ **Example**:
573
+ ```ruby
574
+ on :temperature, value: between(70, 90)
575
+ # Equivalent to: value: ->(v) { v >= 70 && v <= 90 }
576
+
577
+ # Also works with Range objects:
578
+ on :temperature, value: range(70..90)
579
+ ```
580
+
581
+ ---
582
+
583
+ ### Collection Helpers
584
+
585
+ #### `one_of(*values)`
586
+
587
+ Matches if value is one of the specified values.
588
+
589
+ **Example**:
590
+ ```ruby
591
+ on :order, status: one_of("pending", "processing", "completed")
592
+ # Equivalent to: status: ->(s) { ["pending", "processing", "completed"].include?(s) }
593
+ ```
594
+
595
+ ---
596
+
597
+ #### `any(*values)`
598
+
599
+ - With arguments: Same as `one_of`
600
+ - Without arguments: Matches any value (always true)
601
+
602
+ **Example**:
603
+ ```ruby
604
+ # Match one of several values
605
+ on :priority, level: any(1, 2, 3)
606
+
607
+ # Match any value (always true)
608
+ on :metadata, extra_data: any
609
+ ```
610
+
611
+ ---
612
+
613
+ ### String Helpers
614
+
615
+ #### `matches(pattern)`
616
+
617
+ Matches strings against a regular expression.
618
+
619
+ **Example**:
620
+ ```ruby
621
+ on :email, address: matches(/@example\.com$/)
622
+ # Equivalent to: address: ->(a) { a.match?(/@example\.com$/) }
623
+
624
+ on :sensor, name: matches(/^temp_\d+$/)
625
+ ```
626
+
627
+ ---
628
+
629
+ ### Custom Predicates
630
+
631
+ #### `satisfies(&block)`
632
+
633
+ Creates a custom predicate from a block.
634
+
635
+ **Example**:
636
+ ```ruby
637
+ on :order, total: satisfies { |t| t > 100 && t % 10 == 0 }
638
+ # Equivalent to: total: ->(t) { t > 100 && t % 10 == 0 }
639
+ ```
640
+
641
+ ---
642
+
643
+ ## Variable Binding
644
+
645
+ Variables allow you to:
646
+ 1. Capture attribute values for use in actions
647
+ 2. Create join tests between conditions
648
+
649
+ ### Variable Syntax
650
+
651
+ Variables are symbols starting with `?`:
652
+
653
+ ```ruby
654
+ :temp? # Variable named "temp"
655
+ :location? # Variable named "location"
656
+ :pid? # Variable named "pid"
657
+ ```
658
+
659
+ ---
660
+
661
+ ### Capturing Values
662
+
663
+ ```ruby
664
+ rule "temperature_report" do
665
+ on :temperature, value: :temp?, location: :loc?, timestamp: :time?
666
+
667
+ perform do |bindings|
668
+ puts "Temperature at #{bindings[:loc?]}: #{bindings[:temp?]}°F"
669
+ puts "Recorded: #{bindings[:time?]}"
670
+ end
671
+ end
672
+ ```
673
+
674
+ ---
675
+
676
+ ### Join Tests
677
+
678
+ Variables with the same name in different conditions create a join test:
679
+
680
+ ```ruby
681
+ rule "check_inventory" do
682
+ on :order, product_id: :pid?, quantity: :qty?
683
+ on :inventory, product_id: :pid?, available: :avail?
684
+
685
+ perform do |bindings|
686
+ if bindings[:avail?] < bindings[:qty?]
687
+ puts "Insufficient inventory for product #{bindings[:pid?]}"
688
+ end
689
+ end
690
+ end
691
+
692
+ # This rule only fires when:
693
+ # 1. An order fact exists
694
+ # 2. An inventory fact exists
695
+ # 3. Both facts have the SAME product_id
696
+ ```
697
+
698
+ ---
699
+
700
+ ### Multiple Bindings
701
+
702
+ ```ruby
703
+ rule "sensor_temperature_correlation" do
704
+ on :sensor, id: :sensor_id?, location: :loc?, status: "active"
705
+ on :temperature, sensor_id: :sensor_id?, value: :temp?
706
+ on :reading, sensor_id: :sensor_id?, timestamp: :time?
707
+
708
+ perform do |bindings|
709
+ # All three facts share the same sensor_id
710
+ puts "Sensor #{bindings[:sensor_id?]} at #{bindings[:loc?]}"
711
+ puts "Reading: #{bindings[:temp?]}°F at #{bindings[:time?]}"
712
+ end
713
+ end
714
+ ```
715
+
716
+ ---
717
+
718
+ ## Negation
719
+
720
+ Negation matches when a pattern is **absent** from working memory.
721
+
722
+ ### Negation Keywords
723
+
724
+ All of these are **aliases**:
725
+
726
+ - **`without(type, pattern = {})`** - Primary negation keyword
727
+ - **`absent(type, pattern = {})`** - Alias
728
+ - **`missing(type, pattern = {})`** - Alias
729
+ - **`lacks(type, pattern = {})`** - Alias
730
+
731
+ ---
732
+
733
+ ### Direct Negation
734
+
735
+ ```ruby
736
+ # Fire when there is NO alert fact
737
+ rule "all_clear" do
738
+ on :system, status: "running"
739
+ without :alert
740
+ perform { puts "All systems normal" }
741
+ end
742
+
743
+ # Fire when there is NO critical alert
744
+ rule "no_critical_alerts" do
745
+ without :alert, level: "critical"
746
+ perform { puts "No critical alerts" }
747
+ end
748
+
749
+ # Using aliases
750
+ absent :error
751
+ missing :problem, severity: "high"
752
+ lacks :maintenance_flag
753
+ ```
754
+
755
+ ---
756
+
757
+ ### Chained Negation
758
+
759
+ Use `without` (without arguments) followed by `on`:
760
+
761
+ ```ruby
762
+ rule "example" do
763
+ on :order, status: "pending"
764
+ without.on :inventory, quantity: 0
765
+ perform { puts "Order can be fulfilled" }
766
+ end
767
+ ```
768
+
769
+ ---
770
+
771
+ ### Negation with Variables
772
+
773
+ Variables in negated conditions create "there is no fact with this value" tests:
774
+
775
+ ```ruby
776
+ rule "no_matching_inventory" do
777
+ on :order, product_id: :pid?
778
+ without :inventory, product_id: :pid?
779
+
780
+ perform do |bindings|
781
+ puts "No inventory for product #{bindings[:pid?]}"
782
+ end
783
+ end
784
+
785
+ # Fires when:
786
+ # 1. An order exists with product_id=X
787
+ # 2. NO inventory fact exists with product_id=X
788
+ ```
789
+
790
+ ---
791
+
792
+ ### Negation Examples
793
+
794
+ ```ruby
795
+ # Guard condition - only process if no errors
796
+ rule "process_order" do
797
+ on :order, status: "pending"
798
+ without :error
799
+ perform { process_order }
800
+ end
801
+
802
+ # Detect missing required fact
803
+ rule "missing_configuration" do
804
+ on :system, initialized: true
805
+ without :config, loaded: true
806
+ perform { puts "WARNING: Configuration not loaded" }
807
+ end
808
+
809
+ # Timeout detection
810
+ rule "sensor_timeout" do
811
+ on :sensor, id: :sensor_id?, expected: true
812
+ without :reading, sensor_id: :sensor_id?
813
+ perform { |b| puts "Sensor #{b[:sensor_id?]} timeout" }
814
+ end
815
+ ```
816
+
817
+ ---
818
+
819
+ ## Actions
820
+
821
+ Actions define what happens when all conditions match.
822
+
823
+ ### Action Keywords
824
+
825
+ All of these are **aliases**:
826
+
827
+ - **`perform(&block)`** - Primary action keyword
828
+ - **`action(&block)`** - Alias
829
+ - **`execute(&block)`** - Alias
830
+ - **`then(&block)`** - Alias
831
+
832
+ ---
833
+
834
+ ### Action Block
835
+
836
+ Actions receive a `bindings` hash containing all variable bindings:
837
+
838
+ ```ruby
839
+ rule "example" do
840
+ on :temperature, value: :temp?, location: :loc?
841
+
842
+ perform do |bindings|
843
+ temp = bindings[:temp?]
844
+ location = bindings[:loc?]
845
+ puts "Temperature at #{location}: #{temp}°F"
846
+ end
847
+ end
848
+ ```
849
+
850
+ ---
851
+
852
+ ### Action Capabilities
853
+
854
+ Actions can:
855
+
856
+ 1. **Read bindings**:
857
+ ```ruby
858
+ perform do |bindings|
859
+ value = bindings[:temp?]
860
+ end
861
+ ```
862
+
863
+ 2. **Access the knowledge base** (via closure):
864
+ ```ruby
865
+ kb = KBS.knowledge_base do
866
+ rule "add_fact_from_action" do
867
+ on :trigger, event: "start"
868
+ perform do
869
+ fact :process, status: "running" # Add new fact
870
+ end
871
+ end
872
+ end
873
+ ```
874
+
875
+ 3. **Call external methods**:
876
+ ```ruby
877
+ perform do |bindings|
878
+ send_email_alert(bindings[:temp?])
879
+ log_to_database(bindings)
880
+ trigger_alarm if bindings[:level?] == "critical"
881
+ end
882
+ ```
883
+
884
+ 4. **Add/remove facts**:
885
+ ```ruby
886
+ perform do |bindings|
887
+ # Add derived fact
888
+ fact :alert, level: "high", source: bindings[:sensor_id?]
889
+
890
+ # Remove triggering fact
891
+ old_fact = query(:trigger, event: "start").first
892
+ retract old_fact if old_fact
893
+ end
894
+ ```
895
+
896
+ ---
897
+
898
+ ### Action Examples
899
+
900
+ ```ruby
901
+ # Simple logging
902
+ rule "log_temperature" do
903
+ on :temperature, value: :temp?
904
+ perform { |b| puts "Temperature: #{b[:temp?]}" }
905
+ end
906
+
907
+ # State machine transition
908
+ rule "pending_to_processing" do
909
+ on :order, id: :order_id?, status: "pending"
910
+ on :worker, status: "available", id: :worker_id?
911
+
912
+ perform do |bindings|
913
+ # Update order status
914
+ order = query(:order, id: bindings[:order_id?]).first
915
+ retract order
916
+ fact :order, id: bindings[:order_id?],
917
+ status: "processing",
918
+ worker_id: bindings[:worker_id?]
919
+
920
+ # Update worker status
921
+ worker = query(:worker, id: bindings[:worker_id?]).first
922
+ retract worker
923
+ fact :worker, id: bindings[:worker_id?], status: "busy"
924
+ end
925
+ end
926
+
927
+ # Aggregation
928
+ rule "daily_summary" do
929
+ on :trigger, event: "end_of_day"
930
+
931
+ perform do
932
+ temps = query(:temperature).map { |f| f[:value] }
933
+ avg = temps.sum / temps.size.to_f
934
+
935
+ fact :daily_summary,
936
+ date: Date.today,
937
+ avg_temp: avg,
938
+ max_temp: temps.max,
939
+ min_temp: temps.min
940
+ end
941
+ end
942
+ ```
943
+
944
+ ---
945
+
946
+ ## Working with Facts
947
+
948
+ ### Adding Facts
949
+
950
+ ```ruby
951
+ kb = KBS.knowledge_base do
952
+ # During initialization
953
+ fact :temperature, value: 85, location: "server_room"
954
+ fact :sensor, id: 1, status: "active"
955
+
956
+ # Or from action blocks
957
+ rule "add_derived_fact" do
958
+ on :temperature, value: greater_than(80)
959
+ perform do
960
+ fact :alert, level: "high", timestamp: Time.now
961
+ end
962
+ end
963
+ end
964
+
965
+ # After creation
966
+ kb.fact :temperature, value: 90
967
+ kb.assert :humidity, value: 60 # Alias
968
+ ```
969
+
970
+ ---
971
+
972
+ ### Removing Facts
973
+
974
+ ```ruby
975
+ kb = KBS.knowledge_base do
976
+ temp = fact :temperature, value: 85
977
+
978
+ # Remove specific fact
979
+ retract temp
980
+
981
+ # Remove from action
982
+ rule "cleanup" do
983
+ on :temperature, timestamp: less_than(Time.now - 3600)
984
+ perform do
985
+ old_facts = query(:temperature)
986
+ .select { |f| f[:timestamp] < Time.now - 3600 }
987
+ old_facts.each { |f| retract f }
988
+ end
989
+ end
990
+ end
991
+ ```
992
+
993
+ ---
994
+
995
+ ### Querying Facts
996
+
997
+ ```ruby
998
+ kb = KBS.knowledge_base do
999
+ fact :temperature, value: 85, location: "server_room"
1000
+ fact :temperature, value: 75, location: "lobby"
1001
+ fact :humidity, value: 60, location: "server_room"
1002
+
1003
+ # Get all facts
1004
+ all = facts
1005
+
1006
+ # Query by type
1007
+ temps = query(:temperature)
1008
+
1009
+ # Query by type and attributes
1010
+ server_room_temps = query(:temperature, location: "server_room")
1011
+
1012
+ # Use query results in actions
1013
+ rule "check_average" do
1014
+ on :trigger, event: "calculate_average"
1015
+
1016
+ perform do
1017
+ temps = query(:temperature).map { |f| f[:value] }
1018
+ avg = temps.sum / temps.size.to_f
1019
+ puts "Average temperature: #{avg.round(1)}°F"
1020
+ end
1021
+ end
1022
+ end
1023
+ ```
1024
+
1025
+ ---
1026
+
1027
+ ## Introspection
1028
+
1029
+ ### Inspecting Facts
1030
+
1031
+ ```ruby
1032
+ kb = KBS.knowledge_base do
1033
+ fact :temperature, value: 85
1034
+ fact :humidity, value: 60
1035
+
1036
+ print_facts
1037
+ end
1038
+
1039
+ # Output:
1040
+ # Working Memory Contents:
1041
+ # ----------------------------------------
1042
+ # 1. temperature(value: 85)
1043
+ # 2. humidity(value: 60)
1044
+ # ----------------------------------------
1045
+ ```
1046
+
1047
+ ---
1048
+
1049
+ ### Inspecting Rules
1050
+
1051
+ ```ruby
1052
+ kb = KBS.knowledge_base do
1053
+ rule "high_temp" do
1054
+ desc "Alert on high temperature"
1055
+ priority 10
1056
+ on :temperature, value: greater_than(80)
1057
+ perform { puts "High!" }
1058
+ end
1059
+
1060
+ print_rules
1061
+ end
1062
+
1063
+ # Output:
1064
+ # Knowledge Base Rules:
1065
+ # ----------------------------------------
1066
+ # Rule: high_temp
1067
+ # Description: Alert on high temperature
1068
+ # Priority: 10
1069
+ # Conditions: 1
1070
+ # 1. temperature({:value=>#<Proc:...>})
1071
+ # ----------------------------------------
1072
+ ```
1073
+
1074
+ ---
1075
+
1076
+ ### Programmatic Access
1077
+
1078
+ ```ruby
1079
+ kb = KBS.knowledge_base do
1080
+ rule "example" do
1081
+ on :temperature, value: :temp?
1082
+ perform { |b| puts b[:temp?] }
1083
+ end
1084
+ end
1085
+
1086
+ # Access rules
1087
+ kb.rules # => Hash { "example" => KBS::Rule }
1088
+ kb.rules["example"] # => KBS::Rule instance
1089
+
1090
+ # Access engine
1091
+ kb.engine # => KBS::Engine
1092
+ kb.engine.working_memory # => KBS::WorkingMemory
1093
+ kb.engine.rules # => Array<KBS::Rule>
1094
+ ```
1095
+
1096
+ ---
1097
+
1098
+ ## Complete Examples
1099
+
1100
+ ### Temperature Monitoring
1101
+
1102
+ ```ruby
1103
+ require 'kbs'
1104
+
1105
+ kb = KBS.knowledge_base do
1106
+ # Rules
1107
+ rule "high_temperature_alert" do
1108
+ desc "Alert when temperature exceeds safe threshold"
1109
+ priority 10
1110
+
1111
+ on :sensor, id: :sensor_id?, status: "active"
1112
+ on :temperature, sensor_id: :sensor_id?, value: greater_than(80)
1113
+ without :alert, sensor_id: :sensor_id? # No existing alert
1114
+
1115
+ perform do |bindings|
1116
+ puts "⚠️ HIGH TEMPERATURE ALERT"
1117
+ puts "Sensor: #{bindings[:sensor_id?]}"
1118
+ puts "Temperature: #{bindings[:value?]}°F"
1119
+
1120
+ # Create alert fact
1121
+ fact :alert,
1122
+ sensor_id: bindings[:sensor_id?],
1123
+ level: "high",
1124
+ timestamp: Time.now
1125
+ end
1126
+ end
1127
+
1128
+ rule "temperature_normal" do
1129
+ desc "Clear alert when temperature returns to normal"
1130
+ priority 5
1131
+
1132
+ on :temperature, sensor_id: :sensor_id?, value: less_than(75)
1133
+ on :alert, sensor_id: :sensor_id?
1134
+
1135
+ perform do |bindings|
1136
+ puts "✓ Temperature normal for sensor #{bindings[:sensor_id?]}"
1137
+
1138
+ # Remove alert
1139
+ alerts = query(:alert, sensor_id: bindings[:sensor_id?])
1140
+ alerts.each { |a| retract a }
1141
+ end
1142
+ end
1143
+
1144
+ # Initial facts
1145
+ fact :sensor, id: 1, status: "active", location: "server_room"
1146
+ fact :sensor, id: 2, status: "active", location: "lobby"
1147
+
1148
+ # Simulate readings
1149
+ fact :temperature, sensor_id: 1, value: 85 # Will trigger alert
1150
+ fact :temperature, sensor_id: 2, value: 72 # Normal
1151
+
1152
+ run
1153
+
1154
+ print_facts
1155
+ end
1156
+ ```
1157
+
1158
+ ---
1159
+
1160
+ ### Order Processing Workflow
1161
+
1162
+ ```ruby
1163
+ kb = KBS.knowledge_base do
1164
+ rule "validate_order" do
1165
+ priority 100
1166
+
1167
+ on :order, id: :order_id?, status: "new", product_id: :pid?, quantity: :qty?
1168
+ on :inventory, product_id: :pid?, quantity: :available?
1169
+
1170
+ perform do |bindings|
1171
+ if bindings[:available?] >= bindings[:qty?]
1172
+ order = query(:order, id: bindings[:order_id?]).first
1173
+ retract order
1174
+ fact :order,
1175
+ id: bindings[:order_id?],
1176
+ status: "validated",
1177
+ product_id: bindings[:pid?],
1178
+ quantity: bindings[:qty?]
1179
+ else
1180
+ fact :alert,
1181
+ type: "insufficient_inventory",
1182
+ order_id: bindings[:order_id?]
1183
+ end
1184
+ end
1185
+ end
1186
+
1187
+ rule "fulfill_order" do
1188
+ priority 50
1189
+
1190
+ on :order, id: :order_id?, status: "validated",
1191
+ product_id: :pid?, quantity: :qty?
1192
+ on :inventory, product_id: :pid?, quantity: :available?
1193
+
1194
+ perform do |bindings|
1195
+ # Deduct inventory
1196
+ inventory = query(:inventory, product_id: bindings[:pid?]).first
1197
+ retract inventory
1198
+ fact :inventory,
1199
+ product_id: bindings[:pid?],
1200
+ quantity: bindings[:available?] - bindings[:qty?]
1201
+
1202
+ # Update order status
1203
+ order = query(:order, id: bindings[:order_id?]).first
1204
+ retract order
1205
+ fact :order,
1206
+ id: bindings[:order_id?],
1207
+ status: "fulfilled",
1208
+ product_id: bindings[:pid?],
1209
+ quantity: bindings[:qty?]
1210
+
1211
+ puts "✓ Order #{bindings[:order_id?]} fulfilled"
1212
+ end
1213
+ end
1214
+
1215
+ # Initial state
1216
+ fact :inventory, product_id: "ABC", quantity: 100
1217
+ fact :inventory, product_id: "XYZ", quantity: 50
1218
+
1219
+ fact :order, id: 1, status: "new", product_id: "ABC", quantity: 10
1220
+ fact :order, id: 2, status: "new", product_id: "XYZ", quantity: 60 # Insufficient!
1221
+
1222
+ run
1223
+ print_facts
1224
+ end
1225
+ ```
1226
+
1227
+ ---
1228
+
1229
+ ## Best Practices
1230
+
1231
+ ### 1. Use Descriptive Names
1232
+
1233
+ ```ruby
1234
+ # Good
1235
+ rule "high_temperature_alert" do
1236
+ desc "Alert when server room temperature exceeds 80°F"
1237
+ # ...
1238
+ end
1239
+
1240
+ # Bad
1241
+ rule "rule1" do
1242
+ # ...
1243
+ end
1244
+ ```
1245
+
1246
+ ---
1247
+
1248
+ ### 2. Add Descriptions
1249
+
1250
+ ```ruby
1251
+ rule "complex_calculation" do
1252
+ desc "Calculates portfolio value using current market prices and holdings"
1253
+ # ... complex logic ...
1254
+ end
1255
+ ```
1256
+
1257
+ ---
1258
+
1259
+ ### 3. Order Conditions by Selectivity
1260
+
1261
+ ```ruby
1262
+ # Good - Most selective first
1263
+ rule "specific_sensor_alert" do
1264
+ on :sensor, id: 42, status: "active" # Very selective
1265
+ on :temperature, sensor_id: 42, value: greater_than(80)
1266
+ perform { puts "Alert!" }
1267
+ end
1268
+
1269
+ # Less efficient - Unselective first
1270
+ rule "specific_sensor_alert" do
1271
+ on :temperature, value: greater_than(80) # Matches many facts
1272
+ on :sensor, id: 42, status: "active"
1273
+ perform { puts "Alert!" }
1274
+ end
1275
+ ```
1276
+
1277
+ ---
1278
+
1279
+ ### 4. Use Pattern Helpers
1280
+
1281
+ ```ruby
1282
+ # Good - Readable
1283
+ on :temperature, value: between(70, 90)
1284
+ on :order, status: one_of("pending", "processing")
1285
+
1286
+ # Less readable
1287
+ on :temperature, value: ->(v) { v >= 70 && v <= 90 }
1288
+ on :order, status: ->(s) { ["pending", "processing"].include?(s) }
1289
+ ```
1290
+
1291
+ ---
1292
+
1293
+ ### 5. Keep Actions Simple
1294
+
1295
+ ```ruby
1296
+ # Good - Simple, focused action
1297
+ rule "log_temperature" do
1298
+ on :temperature, value: :temp?
1299
+ perform { |b| logger.info("Temperature: #{b[:temp?]}") }
1300
+ end
1301
+
1302
+ # Avoid - Complex logic in action
1303
+ rule "complex_action" do
1304
+ on :temperature, value: :temp?
1305
+ perform do |b|
1306
+ # 100 lines of complex logic...
1307
+ # Better to extract to methods
1308
+ end
1309
+ end
1310
+ ```
1311
+
1312
+ ---
1313
+
1314
+ ## See Also
1315
+
1316
+ - [Getting Started Guide](getting-started.md) - First tutorial
1317
+ - [Writing Rules Guide](writing-rules.md) - Best practices for rules
1318
+ - [Pattern Matching Guide](pattern-matching.md) - Advanced pattern matching
1319
+ - [Variable Binding Guide](variable-binding.md) - Join tests and bindings
1320
+ - [Negation Guide](negation.md) - Negation semantics
1321
+ - [Rules API](../api/rules.md) - Programmatic rule creation