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.
- checksums.yaml +4 -4
- data/.github/workflows/deploy-github-pages.yml +52 -0
- data/CHANGELOG.md +68 -2
- data/README.md +235 -334
- data/docs/DOCUMENTATION_STATUS.md +158 -0
- data/docs/advanced/custom-persistence.md +775 -0
- data/docs/advanced/debugging.md +726 -0
- data/docs/advanced/index.md +8 -0
- data/docs/advanced/performance.md +832 -0
- data/docs/advanced/testing.md +691 -0
- data/docs/api/blackboard.md +1157 -0
- data/docs/api/engine.md +978 -0
- data/docs/api/facts.md +1212 -0
- data/docs/api/index.md +12 -0
- data/docs/api/rules.md +1034 -0
- data/docs/architecture/blackboard.md +553 -0
- data/docs/architecture/index.md +277 -0
- data/docs/architecture/network-structure.md +343 -0
- data/docs/architecture/rete-algorithm.md +737 -0
- data/docs/assets/css/custom.css +83 -0
- data/docs/assets/images/blackboard-architecture.svg +136 -0
- data/docs/assets/images/compiled-network.svg +101 -0
- data/docs/assets/images/fact-assertion-flow.svg +117 -0
- data/docs/assets/images/kbs.jpg +0 -0
- data/docs/assets/images/pattern-matching-trace.svg +136 -0
- data/docs/assets/images/rete-network-layers.svg +96 -0
- data/docs/assets/images/system-layers.svg +69 -0
- data/docs/assets/images/trading-signal-network.svg +139 -0
- data/docs/assets/js/mathjax.js +17 -0
- data/docs/examples/expert-systems.md +1031 -0
- data/docs/examples/index.md +9 -0
- data/docs/examples/multi-agent.md +1335 -0
- data/docs/examples/stock-trading.md +488 -0
- data/docs/guides/blackboard-memory.md +558 -0
- data/docs/guides/dsl.md +1321 -0
- data/docs/guides/facts.md +652 -0
- data/docs/guides/getting-started.md +383 -0
- data/docs/guides/index.md +23 -0
- data/docs/guides/negation.md +529 -0
- data/docs/guides/pattern-matching.md +561 -0
- data/docs/guides/persistence.md +451 -0
- data/docs/guides/variable-binding.md +491 -0
- data/docs/guides/writing-rules.md +755 -0
- data/docs/index.md +157 -0
- data/docs/installation.md +156 -0
- data/docs/quick-start.md +228 -0
- data/examples/README.md +2 -2
- data/examples/advanced_example.rb +2 -2
- data/examples/advanced_example_dsl.rb +224 -0
- data/examples/ai_enhanced_kbs.rb +1 -1
- data/examples/ai_enhanced_kbs_dsl.rb +538 -0
- data/examples/blackboard_demo_dsl.rb +50 -0
- data/examples/car_diagnostic.rb +1 -1
- data/examples/car_diagnostic_dsl.rb +54 -0
- data/examples/concurrent_inference_demo.rb +5 -5
- data/examples/concurrent_inference_demo_dsl.rb +363 -0
- data/examples/csv_trading_system.rb +1 -1
- data/examples/csv_trading_system_dsl.rb +525 -0
- data/examples/knowledge_base.db +0 -0
- data/examples/portfolio_rebalancing_system.rb +2 -2
- data/examples/portfolio_rebalancing_system_dsl.rb +613 -0
- data/examples/redis_trading_demo_dsl.rb +177 -0
- data/examples/run_all.rb +50 -0
- data/examples/run_all_dsl.rb +49 -0
- data/examples/stock_trading_advanced.rb +1 -1
- data/examples/stock_trading_advanced_dsl.rb +404 -0
- data/examples/temp.txt +7693 -0
- data/examples/temp_dsl.txt +8447 -0
- data/examples/timestamped_trading.rb +1 -1
- data/examples/timestamped_trading_dsl.rb +258 -0
- data/examples/trading_demo.rb +1 -1
- data/examples/trading_demo_dsl.rb +322 -0
- data/examples/working_demo.rb +1 -1
- data/examples/working_demo_dsl.rb +160 -0
- data/lib/kbs/blackboard/engine.rb +3 -3
- data/lib/kbs/blackboard/fact.rb +1 -1
- data/lib/kbs/condition.rb +1 -1
- data/lib/kbs/dsl/knowledge_base.rb +1 -1
- data/lib/kbs/dsl/variable.rb +1 -1
- data/lib/kbs/{rete_engine.rb → engine.rb} +1 -1
- data/lib/kbs/fact.rb +1 -1
- data/lib/kbs/version.rb +1 -1
- data/lib/kbs.rb +2 -2
- data/mkdocs.yml +181 -0
- metadata +66 -6
- data/examples/stock_trading_system.rb.bak +0 -563
|
@@ -0,0 +1,553 @@
|
|
|
1
|
+
# Blackboard Architecture
|
|
2
|
+
|
|
3
|
+
The Blackboard pattern is a powerful approach to multi-agent problem-solving where independent knowledge sources collaborate through a shared workspace.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+

|
|
8
|
+
|
|
9
|
+
*Blackboard system with multiple agents reading and writing to shared persistent memory with pluggable storage backends.*
|
|
10
|
+
|
|
11
|
+
## Core Concept
|
|
12
|
+
|
|
13
|
+
The blackboard architecture consists of three main components:
|
|
14
|
+
|
|
15
|
+
1. **Blackboard (Memory)**: Central shared workspace for facts
|
|
16
|
+
2. **Knowledge Sources (Agents)**: Independent experts that read and write facts
|
|
17
|
+
3. **Control Component**: Coordinates agent execution (often via rules)
|
|
18
|
+
|
|
19
|
+
Agents operate asynchronously, triggered by changes to the blackboard state. Each agent:
|
|
20
|
+
- Reads relevant facts from the blackboard
|
|
21
|
+
- Performs reasoning or computation
|
|
22
|
+
- Writes conclusions back to the blackboard
|
|
23
|
+
- Triggers other agents via fact changes
|
|
24
|
+
|
|
25
|
+
## KBS Implementation
|
|
26
|
+
|
|
27
|
+
### Blackboard::Memory
|
|
28
|
+
|
|
29
|
+
The central workspace that replaces `WorkingMemory` with persistence:
|
|
30
|
+
|
|
31
|
+
```ruby
|
|
32
|
+
require 'kbs/blackboard'
|
|
33
|
+
|
|
34
|
+
# Create blackboard with SQLite backend
|
|
35
|
+
memory = KBS::Blackboard::Memory.new(db_path: 'knowledge_base.db')
|
|
36
|
+
|
|
37
|
+
# Add facts (persisted automatically)
|
|
38
|
+
fact = memory.add_fact(:stock, {
|
|
39
|
+
symbol: "AAPL",
|
|
40
|
+
price: 150.50,
|
|
41
|
+
timestamp: Time.now
|
|
42
|
+
})
|
|
43
|
+
# => #<KBS::Blackboard::Fact uuid="abc-123" ...>
|
|
44
|
+
|
|
45
|
+
# Query facts
|
|
46
|
+
stocks = memory.facts_of_type(:stock)
|
|
47
|
+
# => [#<KBS::Blackboard::Fact ...>, ...]
|
|
48
|
+
|
|
49
|
+
# Facts survive process restart
|
|
50
|
+
memory2 = KBS::Blackboard::Memory.new(db_path: 'knowledge_base.db')
|
|
51
|
+
memory2.facts_of_type(:stock)
|
|
52
|
+
# => Still there!
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
**Implementation**: `lib/kbs/blackboard/memory.rb`
|
|
56
|
+
|
|
57
|
+
### Blackboard::Engine
|
|
58
|
+
|
|
59
|
+
RETE engine with persistent blackboard memory:
|
|
60
|
+
|
|
61
|
+
```ruby
|
|
62
|
+
# Create engine with blackboard
|
|
63
|
+
engine = KBS::Blackboard::Engine.new(db_path: 'trading.db')
|
|
64
|
+
|
|
65
|
+
# Define rules (persisted in the database)
|
|
66
|
+
engine.add_rule(Rule.new("buy_signal") do |r|
|
|
67
|
+
r.conditions = [
|
|
68
|
+
Condition.new(:stock, { symbol: :sym?, price: :price? }),
|
|
69
|
+
Condition.new(:threshold, { symbol: :sym?, max: :max? })
|
|
70
|
+
]
|
|
71
|
+
|
|
72
|
+
r.action = lambda do |facts, bindings|
|
|
73
|
+
if bindings[:price?] < bindings[:max?]
|
|
74
|
+
# Write new fact to blackboard
|
|
75
|
+
engine.add_fact(:order, {
|
|
76
|
+
symbol: bindings[:sym?],
|
|
77
|
+
action: "BUY",
|
|
78
|
+
price: bindings[:price?]
|
|
79
|
+
})
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end)
|
|
83
|
+
|
|
84
|
+
# Facts trigger rules, which create new facts
|
|
85
|
+
engine.add_fact(:stock, symbol: "AAPL", price: 145.0)
|
|
86
|
+
engine.add_fact(:threshold, symbol: "AAPL", max: 150.0)
|
|
87
|
+
engine.run
|
|
88
|
+
# => Creates :order fact in blackboard
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
**Implementation**: `lib/kbs/blackboard/engine.rb`
|
|
92
|
+
|
|
93
|
+
### Message Queue
|
|
94
|
+
|
|
95
|
+
Priority-based communication between agents:
|
|
96
|
+
|
|
97
|
+
```ruby
|
|
98
|
+
memory = KBS::Blackboard::Memory.new(db_path: 'system.db')
|
|
99
|
+
queue = memory.message_queue
|
|
100
|
+
|
|
101
|
+
# Agent 1: Post high-priority message
|
|
102
|
+
queue.post("risk_analysis", {
|
|
103
|
+
alert: "High volatility detected",
|
|
104
|
+
severity: "critical"
|
|
105
|
+
}, priority: 10)
|
|
106
|
+
|
|
107
|
+
# Agent 2: Read and process messages
|
|
108
|
+
messages = queue.read("risk_analysis", limit: 5)
|
|
109
|
+
messages.each do |msg|
|
|
110
|
+
puts "Processing: #{msg[:data][:alert]}"
|
|
111
|
+
queue.acknowledge(msg[:id])
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# Unacknowledged messages remain in queue
|
|
115
|
+
pending = queue.pending("risk_analysis")
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
**Implementation**: `lib/kbs/blackboard/message_queue.rb`
|
|
119
|
+
|
|
120
|
+
### Audit Log
|
|
121
|
+
|
|
122
|
+
Complete history of all changes for compliance and debugging:
|
|
123
|
+
|
|
124
|
+
```ruby
|
|
125
|
+
memory = KBS::Blackboard::Memory.new(db_path: 'audit.db')
|
|
126
|
+
audit = memory.audit_log
|
|
127
|
+
|
|
128
|
+
# All fact changes are logged automatically
|
|
129
|
+
fact = memory.add_fact(:stock, symbol: "AAPL", price: 150)
|
|
130
|
+
memory.update_fact(fact.id, price: 155)
|
|
131
|
+
memory.remove_fact(fact)
|
|
132
|
+
|
|
133
|
+
# Query audit trail
|
|
134
|
+
history = audit.fact_history(fact.id)
|
|
135
|
+
# => [
|
|
136
|
+
# { action: "created", timestamp: ..., data: {price: 150} },
|
|
137
|
+
# { action: "updated", timestamp: ..., data: {price: 155} },
|
|
138
|
+
# { action: "deleted", timestamp: ... }
|
|
139
|
+
# ]
|
|
140
|
+
|
|
141
|
+
# See what rules fired
|
|
142
|
+
rule_log = audit.rules_fired(limit: 10)
|
|
143
|
+
# => [
|
|
144
|
+
# { rule_name: "buy_signal", timestamp: ..., facts: [...] },
|
|
145
|
+
# ...
|
|
146
|
+
# ]
|
|
147
|
+
|
|
148
|
+
# Recent changes across all facts
|
|
149
|
+
recent = audit.recent_changes(limit: 20)
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
**Implementation**: `lib/kbs/blackboard/audit_log.rb`
|
|
153
|
+
|
|
154
|
+
## Persistence Backends
|
|
155
|
+
|
|
156
|
+
### SQLite Store (Default)
|
|
157
|
+
|
|
158
|
+
Best for: Single-process applications, development, small-to-medium data.
|
|
159
|
+
|
|
160
|
+
```ruby
|
|
161
|
+
engine = KBS::Blackboard::Engine.new(db_path: 'kb.db')
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
**Pros:**
|
|
165
|
+
- ✅ Zero configuration (no server needed)
|
|
166
|
+
- ✅ ACID transactions
|
|
167
|
+
- ✅ Durable across restarts
|
|
168
|
+
- ✅ Simple backup (copy .db file)
|
|
169
|
+
- ✅ Full-text search capabilities
|
|
170
|
+
|
|
171
|
+
**Cons:**
|
|
172
|
+
- ⚠️ Slower than Redis (still fast for most use cases)
|
|
173
|
+
- ⚠️ Single-writer limitation
|
|
174
|
+
- ⚠️ Not distributed
|
|
175
|
+
|
|
176
|
+
**Schema:**
|
|
177
|
+
```sql
|
|
178
|
+
CREATE TABLE facts (
|
|
179
|
+
id TEXT PRIMARY KEY,
|
|
180
|
+
fact_type TEXT NOT NULL,
|
|
181
|
+
attributes TEXT NOT NULL, -- JSON
|
|
182
|
+
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
183
|
+
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
184
|
+
);
|
|
185
|
+
|
|
186
|
+
CREATE TABLE audit_log (
|
|
187
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
188
|
+
fact_id TEXT,
|
|
189
|
+
action TEXT NOT NULL,
|
|
190
|
+
timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
191
|
+
data TEXT -- JSON
|
|
192
|
+
);
|
|
193
|
+
|
|
194
|
+
CREATE TABLE messages (
|
|
195
|
+
id TEXT PRIMARY KEY,
|
|
196
|
+
topic TEXT NOT NULL,
|
|
197
|
+
priority INTEGER DEFAULT 0,
|
|
198
|
+
data TEXT NOT NULL, -- JSON
|
|
199
|
+
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
200
|
+
acknowledged BOOLEAN DEFAULT 0
|
|
201
|
+
);
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
**Implementation**: `lib/kbs/blackboard/persistence/sqlite_store.rb`
|
|
205
|
+
|
|
206
|
+
### Redis Store
|
|
207
|
+
|
|
208
|
+
Best for: High-throughput applications, distributed systems, real-time trading.
|
|
209
|
+
|
|
210
|
+
```ruby
|
|
211
|
+
store = KBS::Blackboard::Persistence::RedisStore.new(
|
|
212
|
+
url: 'redis://localhost:6379/0'
|
|
213
|
+
)
|
|
214
|
+
engine = KBS::Blackboard::Engine.new(store: store)
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
**Pros:**
|
|
218
|
+
- ✅ **100x faster** than SQLite for reads/writes
|
|
219
|
+
- ✅ Supports distributed agents (multiple processes, machines)
|
|
220
|
+
- ✅ Built-in pub/sub for real-time notifications
|
|
221
|
+
- ✅ Atomic operations
|
|
222
|
+
- ✅ TTL support for ephemeral facts
|
|
223
|
+
|
|
224
|
+
**Cons:**
|
|
225
|
+
- ⚠️ Requires Redis server
|
|
226
|
+
- ⚠️ Volatile by default (enable RDB/AOF for durability)
|
|
227
|
+
- ⚠️ More complex deployment
|
|
228
|
+
|
|
229
|
+
**Data Structures:**
|
|
230
|
+
```
|
|
231
|
+
# Facts stored as Redis hashes
|
|
232
|
+
fact:{uuid} → { type: "stock", symbol: "AAPL", price: 150 }
|
|
233
|
+
|
|
234
|
+
# Indexes for efficient queries
|
|
235
|
+
facts:type:stock → Set of fact UUIDs
|
|
236
|
+
facts:active → Set of all active fact UUIDs
|
|
237
|
+
|
|
238
|
+
# Messages as sorted sets (by priority)
|
|
239
|
+
messages:risk_alerts → ZSet[(msg1, priority), (msg2, priority), ...]
|
|
240
|
+
|
|
241
|
+
# Audit as lists
|
|
242
|
+
fact_history:{uuid} → List of change records
|
|
243
|
+
rules_fired:all → List of rule executions
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
**Implementation**: `lib/kbs/blackboard/persistence/redis_store.rb`
|
|
247
|
+
|
|
248
|
+
### Hybrid Store
|
|
249
|
+
|
|
250
|
+
Best for: Production systems needing speed + durability + audit.
|
|
251
|
+
|
|
252
|
+
```ruby
|
|
253
|
+
store = KBS::Blackboard::Persistence::HybridStore.new(
|
|
254
|
+
redis_url: 'redis://localhost:6379/0',
|
|
255
|
+
db_path: 'audit.db'
|
|
256
|
+
)
|
|
257
|
+
engine = KBS::Blackboard::Engine.new(store: store)
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
**Strategy:**
|
|
261
|
+
- **Redis**: Facts and messages (fast access)
|
|
262
|
+
- **SQLite**: Audit log (durable history)
|
|
263
|
+
|
|
264
|
+
**Pros:**
|
|
265
|
+
- ✅ Fast fact operations (Redis)
|
|
266
|
+
- ✅ Durable audit trail (SQLite)
|
|
267
|
+
- ✅ Best of both worlds
|
|
268
|
+
|
|
269
|
+
**Cons:**
|
|
270
|
+
- ⚠️ Requires both Redis and SQLite
|
|
271
|
+
- ⚠️ Slightly more complex
|
|
272
|
+
|
|
273
|
+
**Implementation**: `lib/kbs/blackboard/persistence/hybrid_store.rb`
|
|
274
|
+
|
|
275
|
+
## Multi-Agent Example
|
|
276
|
+
|
|
277
|
+
Trading system with four specialized agents:
|
|
278
|
+
|
|
279
|
+
```ruby
|
|
280
|
+
# Shared blackboard
|
|
281
|
+
blackboard = KBS::Blackboard::Engine.new(db_path: 'trading.db')
|
|
282
|
+
|
|
283
|
+
# Agent 1: Market Data Collector
|
|
284
|
+
data_agent = KBS::Rule.new("collect_data", priority: 5) do |r|
|
|
285
|
+
r.conditions = [
|
|
286
|
+
Condition.new(:market_open, { status: true }),
|
|
287
|
+
Condition.new(:stock_data, { symbol: :sym? }, negated: true)
|
|
288
|
+
]
|
|
289
|
+
|
|
290
|
+
r.action = lambda do |facts, bindings|
|
|
291
|
+
price = fetch_current_price(bindings[:sym?])
|
|
292
|
+
blackboard.add_fact(:stock_data, {
|
|
293
|
+
symbol: bindings[:sym?],
|
|
294
|
+
price: price,
|
|
295
|
+
timestamp: Time.now
|
|
296
|
+
})
|
|
297
|
+
end
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
# Agent 2: Signal Generator
|
|
301
|
+
signal_agent = KBS::Rule.new("generate_signals", priority: 10) do |r|
|
|
302
|
+
r.conditions = [
|
|
303
|
+
Condition.new(:stock_data, { symbol: :sym?, price: :price? }),
|
|
304
|
+
Condition.new(:sma_data, { symbol: :sym?, sma: :sma? })
|
|
305
|
+
]
|
|
306
|
+
|
|
307
|
+
r.action = lambda do |facts, bindings|
|
|
308
|
+
if bindings[:price?] > bindings[:sma?]
|
|
309
|
+
blackboard.add_fact(:signal, {
|
|
310
|
+
symbol: bindings[:sym?],
|
|
311
|
+
direction: "BUY",
|
|
312
|
+
strength: (bindings[:price?] / bindings[:sma?]) - 1.0
|
|
313
|
+
})
|
|
314
|
+
end
|
|
315
|
+
end
|
|
316
|
+
end
|
|
317
|
+
|
|
318
|
+
# Agent 3: Risk Manager
|
|
319
|
+
risk_agent = KBS::Rule.new("check_risk", priority: 20) do |r|
|
|
320
|
+
r.conditions = [
|
|
321
|
+
Condition.new(:signal, { symbol: :sym?, direction: :dir? }),
|
|
322
|
+
Condition.new(:portfolio, { symbol: :sym?, position: :pos? })
|
|
323
|
+
]
|
|
324
|
+
|
|
325
|
+
r.action = lambda do |facts, bindings|
|
|
326
|
+
if bindings[:pos?] > 1000 && bindings[:dir?] == "BUY"
|
|
327
|
+
blackboard.add_fact(:risk_alert, {
|
|
328
|
+
symbol: bindings[:sym?],
|
|
329
|
+
reason: "Position limit exceeded"
|
|
330
|
+
})
|
|
331
|
+
else
|
|
332
|
+
blackboard.add_fact(:approved_signal, {
|
|
333
|
+
symbol: bindings[:sym?],
|
|
334
|
+
direction: bindings[:dir?]
|
|
335
|
+
})
|
|
336
|
+
end
|
|
337
|
+
end
|
|
338
|
+
end
|
|
339
|
+
|
|
340
|
+
# Agent 4: Order Executor
|
|
341
|
+
exec_agent = KBS::Rule.new("execute_orders", priority: 30) do |r|
|
|
342
|
+
r.conditions = [
|
|
343
|
+
Condition.new(:approved_signal, { symbol: :sym?, direction: :dir? }),
|
|
344
|
+
Condition.new(:risk_alert, { symbol: :sym? }, negated: true)
|
|
345
|
+
]
|
|
346
|
+
|
|
347
|
+
r.action = lambda do |facts, bindings|
|
|
348
|
+
execute_trade(bindings[:sym?], bindings[:dir?])
|
|
349
|
+
blackboard.add_fact(:execution, {
|
|
350
|
+
symbol: bindings[:sym?],
|
|
351
|
+
direction: bindings[:dir?],
|
|
352
|
+
timestamp: Time.now
|
|
353
|
+
})
|
|
354
|
+
end
|
|
355
|
+
end
|
|
356
|
+
|
|
357
|
+
# Register all agents
|
|
358
|
+
[data_agent, signal_agent, risk_agent, exec_agent].each do |agent|
|
|
359
|
+
blackboard.add_rule(agent)
|
|
360
|
+
end
|
|
361
|
+
|
|
362
|
+
# Trigger the system
|
|
363
|
+
blackboard.add_fact(:market_open, status: true)
|
|
364
|
+
blackboard.add_fact(:portfolio, symbol: "AAPL", position: 500)
|
|
365
|
+
|
|
366
|
+
# Agents collaborate through blackboard
|
|
367
|
+
blackboard.run
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
## Transactions
|
|
371
|
+
|
|
372
|
+
Ensure atomic multi-fact updates:
|
|
373
|
+
|
|
374
|
+
```ruby
|
|
375
|
+
memory = KBS::Blackboard::Memory.new(db_path: 'trades.db')
|
|
376
|
+
|
|
377
|
+
memory.transaction do
|
|
378
|
+
# All or nothing
|
|
379
|
+
order = memory.add_fact(:order, {
|
|
380
|
+
symbol: "AAPL",
|
|
381
|
+
action: "BUY",
|
|
382
|
+
quantity: 100
|
|
383
|
+
})
|
|
384
|
+
|
|
385
|
+
execution = memory.add_fact(:execution, {
|
|
386
|
+
order_id: order.id,
|
|
387
|
+
price: 150.50,
|
|
388
|
+
timestamp: Time.now
|
|
389
|
+
})
|
|
390
|
+
|
|
391
|
+
memory.update_fact(order.id, status: "filled")
|
|
392
|
+
|
|
393
|
+
# If any operation fails, entire transaction rolls back
|
|
394
|
+
end
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
**Nested transactions** are supported via reference counting.
|
|
398
|
+
|
|
399
|
+
## Best Practices
|
|
400
|
+
|
|
401
|
+
### 1. Agent Specialization
|
|
402
|
+
Each agent should focus on one aspect of the problem:
|
|
403
|
+
- ✅ Data collection
|
|
404
|
+
- ✅ Signal generation
|
|
405
|
+
- ✅ Risk assessment
|
|
406
|
+
- ✅ Execution
|
|
407
|
+
|
|
408
|
+
### 2. Priority-Based Execution
|
|
409
|
+
Use rule priorities to ensure correct agent ordering:
|
|
410
|
+
```ruby
|
|
411
|
+
data_collector: priority: 5
|
|
412
|
+
signal_generator: priority: 10
|
|
413
|
+
risk_manager: priority: 20
|
|
414
|
+
executor: priority: 30
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
### 3. Fact Versioning
|
|
418
|
+
Include timestamps for temporal reasoning:
|
|
419
|
+
```ruby
|
|
420
|
+
engine.add_fact(:price, {
|
|
421
|
+
symbol: "AAPL",
|
|
422
|
+
value: 150,
|
|
423
|
+
timestamp: Time.now,
|
|
424
|
+
source: "market_data_feed"
|
|
425
|
+
})
|
|
426
|
+
```
|
|
427
|
+
|
|
428
|
+
### 4. Message Acknowledgment
|
|
429
|
+
Always acknowledge processed messages:
|
|
430
|
+
```ruby
|
|
431
|
+
messages = queue.read("alerts", limit: 10)
|
|
432
|
+
messages.each do |msg|
|
|
433
|
+
process_alert(msg[:data])
|
|
434
|
+
queue.acknowledge(msg[:id]) # Important!
|
|
435
|
+
end
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
### 5. Audit Everything
|
|
439
|
+
Use audit log for debugging and compliance:
|
|
440
|
+
```ruby
|
|
441
|
+
# When something goes wrong, trace back
|
|
442
|
+
audit = memory.audit_log
|
|
443
|
+
changes = audit.recent_changes(limit: 100)
|
|
444
|
+
changes.each do |change|
|
|
445
|
+
puts "#{change[:timestamp]}: #{change[:action]} on #{change[:fact_type]}"
|
|
446
|
+
end
|
|
447
|
+
```
|
|
448
|
+
|
|
449
|
+
## Performance Tuning
|
|
450
|
+
|
|
451
|
+
### Choose the Right Backend
|
|
452
|
+
|
|
453
|
+
| Backend | Use Case | Performance | Durability |
|
|
454
|
+
|---------|----------|-------------|------------|
|
|
455
|
+
| SQLite | Development, single-process | Good | Excellent |
|
|
456
|
+
| Redis | High-frequency trading, distributed | Excellent | Good (with AOF) |
|
|
457
|
+
| Hybrid | Production systems | Excellent | Excellent |
|
|
458
|
+
|
|
459
|
+
### Batch Operations
|
|
460
|
+
|
|
461
|
+
```ruby
|
|
462
|
+
# Bad: Individual adds (slow)
|
|
463
|
+
1000.times do |i|
|
|
464
|
+
memory.add_fact(:reading, sensor: i, value: rand)
|
|
465
|
+
end
|
|
466
|
+
|
|
467
|
+
# Good: Transaction batch (fast)
|
|
468
|
+
memory.transaction do
|
|
469
|
+
1000.times do |i|
|
|
470
|
+
memory.add_fact(:reading, sensor: i, value: rand)
|
|
471
|
+
end
|
|
472
|
+
end
|
|
473
|
+
```
|
|
474
|
+
|
|
475
|
+
### Index Strategy (SQLite)
|
|
476
|
+
|
|
477
|
+
```sql
|
|
478
|
+
-- Add indexes for frequent queries
|
|
479
|
+
CREATE INDEX idx_facts_type ON facts(fact_type);
|
|
480
|
+
CREATE INDEX idx_facts_created ON facts(created_at);
|
|
481
|
+
CREATE INDEX idx_messages_topic ON messages(topic, priority);
|
|
482
|
+
```
|
|
483
|
+
|
|
484
|
+
### Redis Memory Management
|
|
485
|
+
|
|
486
|
+
```ruby
|
|
487
|
+
# Set TTL for ephemeral facts
|
|
488
|
+
store = KBS::Blackboard::Persistence::RedisStore.new(
|
|
489
|
+
url: 'redis://localhost:6379/0',
|
|
490
|
+
ttl: 3600 # Facts expire after 1 hour
|
|
491
|
+
)
|
|
492
|
+
```
|
|
493
|
+
|
|
494
|
+
## Advanced Patterns
|
|
495
|
+
|
|
496
|
+
### Opportunistic Triggering
|
|
497
|
+
|
|
498
|
+
Agents activate when their preconditions are met:
|
|
499
|
+
|
|
500
|
+
```ruby
|
|
501
|
+
# Trigger fires only when specific fact exists
|
|
502
|
+
trigger_rule = Rule.new("on_critical_alert") do |r|
|
|
503
|
+
r.conditions = [
|
|
504
|
+
Condition.new(:alert, { severity: "critical" })
|
|
505
|
+
]
|
|
506
|
+
|
|
507
|
+
r.action = lambda { |facts|
|
|
508
|
+
notify_team(facts[0])
|
|
509
|
+
end
|
|
510
|
+
end
|
|
511
|
+
```
|
|
512
|
+
|
|
513
|
+
### Blackboard Focus
|
|
514
|
+
|
|
515
|
+
Limit agent attention to relevant facts:
|
|
516
|
+
|
|
517
|
+
```ruby
|
|
518
|
+
# Agent only sees recent stock data
|
|
519
|
+
recent_data_rule = Rule.new("analyze_recent") do |r|
|
|
520
|
+
r.conditions = [
|
|
521
|
+
Condition.new(:stock_data, {
|
|
522
|
+
symbol: :sym?,
|
|
523
|
+
timestamp: ->(ts) { Time.now - ts < 300 } # Last 5 minutes
|
|
524
|
+
})
|
|
525
|
+
]
|
|
526
|
+
|
|
527
|
+
r.action = lambda { |facts, bindings|
|
|
528
|
+
# Process recent data only
|
|
529
|
+
}
|
|
530
|
+
end
|
|
531
|
+
```
|
|
532
|
+
|
|
533
|
+
### Conflict Resolution
|
|
534
|
+
|
|
535
|
+
When multiple agents could act, use priorities:
|
|
536
|
+
|
|
537
|
+
```ruby
|
|
538
|
+
# High priority: Stop-loss overrides everything
|
|
539
|
+
stop_loss = Rule.new("stop_loss", priority: 100)
|
|
540
|
+
|
|
541
|
+
# Medium priority: Risk management
|
|
542
|
+
risk_check = Rule.new("risk_check", priority: 50)
|
|
543
|
+
|
|
544
|
+
# Low priority: Normal trading signals
|
|
545
|
+
buy_signal = Rule.new("buy", priority: 10)
|
|
546
|
+
```
|
|
547
|
+
|
|
548
|
+
## Next Steps
|
|
549
|
+
|
|
550
|
+
- **[Network Structure](network-structure.md)** - How blackboard integrates with RETE
|
|
551
|
+
- **[Persistence Guide](../guides/persistence.md)** - Choosing and configuring backends
|
|
552
|
+
- **[Multi-Agent Example](../examples/multi-agent.md)** - Complete working system
|
|
553
|
+
- **[Custom Persistence](../advanced/custom-persistence.md)** - Build your own backend
|