robot_lab 0.0.4 → 0.0.6

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 (77) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +50 -0
  3. data/README.md +64 -6
  4. data/Rakefile +2 -1
  5. data/docs/api/core/index.md +41 -46
  6. data/docs/api/core/memory.md +200 -154
  7. data/docs/api/core/network.md +13 -3
  8. data/docs/api/core/robot.md +38 -26
  9. data/docs/api/core/state.md +55 -73
  10. data/docs/api/index.md +7 -28
  11. data/docs/api/messages/index.md +35 -20
  12. data/docs/api/messages/text-message.md +67 -21
  13. data/docs/api/messages/tool-call-message.md +80 -41
  14. data/docs/api/messages/tool-result-message.md +119 -50
  15. data/docs/api/messages/user-message.md +48 -24
  16. data/docs/architecture/core-concepts.md +10 -15
  17. data/docs/concepts.md +5 -7
  18. data/docs/examples/index.md +2 -2
  19. data/docs/getting-started/configuration.md +80 -0
  20. data/docs/guides/building-robots.md +10 -9
  21. data/docs/guides/creating-networks.md +49 -0
  22. data/docs/guides/index.md +0 -5
  23. data/docs/guides/rails-integration.md +244 -162
  24. data/docs/guides/streaming.md +118 -138
  25. data/docs/index.md +0 -8
  26. data/examples/03_network.rb +10 -7
  27. data/examples/08_llm_config.rb +40 -11
  28. data/examples/09_chaining.rb +45 -6
  29. data/examples/11_network_introspection.rb +30 -7
  30. data/examples/12_message_bus.rb +1 -1
  31. data/examples/14_rusty_circuit/heckler.rb +14 -8
  32. data/examples/14_rusty_circuit/open_mic.rb +5 -3
  33. data/examples/14_rusty_circuit/scout.rb +14 -31
  34. data/examples/15_memory_network_and_bus/editorial_pipeline.rb +1 -1
  35. data/examples/16_writers_room/display.rb +158 -0
  36. data/examples/16_writers_room/output/.gitignore +2 -0
  37. data/examples/16_writers_room/output/opus_001.md +263 -0
  38. data/examples/16_writers_room/output/opus_001_notes.log +470 -0
  39. data/examples/16_writers_room/prompts/writer.md +37 -0
  40. data/examples/16_writers_room/room.rb +150 -0
  41. data/examples/16_writers_room/tools.rb +162 -0
  42. data/examples/16_writers_room/writer.rb +121 -0
  43. data/examples/16_writers_room/writers_room.rb +162 -0
  44. data/lib/generators/robot_lab/templates/initializer.rb.tt +0 -13
  45. data/lib/robot_lab/memory.rb +8 -32
  46. data/lib/robot_lab/network.rb +13 -20
  47. data/lib/robot_lab/robot/bus_messaging.rb +239 -0
  48. data/lib/robot_lab/robot/mcp_management.rb +88 -0
  49. data/lib/robot_lab/robot/template_rendering.rb +130 -0
  50. data/lib/robot_lab/robot.rb +56 -420
  51. data/lib/robot_lab/run_config.rb +184 -0
  52. data/lib/robot_lab/state_proxy.rb +2 -12
  53. data/lib/robot_lab/task.rb +8 -1
  54. data/lib/robot_lab/utils.rb +39 -0
  55. data/lib/robot_lab/version.rb +1 -1
  56. data/lib/robot_lab.rb +29 -8
  57. data/mkdocs.yml +0 -11
  58. metadata +15 -20
  59. data/docs/api/adapters/anthropic.md +0 -121
  60. data/docs/api/adapters/gemini.md +0 -133
  61. data/docs/api/adapters/index.md +0 -104
  62. data/docs/api/adapters/openai.md +0 -134
  63. data/docs/api/history/active-record-adapter.md +0 -275
  64. data/docs/api/history/config.md +0 -284
  65. data/docs/api/history/index.md +0 -128
  66. data/docs/api/history/thread-manager.md +0 -194
  67. data/docs/guides/history.md +0 -359
  68. data/lib/robot_lab/adapters/anthropic.rb +0 -163
  69. data/lib/robot_lab/adapters/base.rb +0 -85
  70. data/lib/robot_lab/adapters/gemini.rb +0 -193
  71. data/lib/robot_lab/adapters/openai.rb +0 -160
  72. data/lib/robot_lab/adapters/registry.rb +0 -81
  73. data/lib/robot_lab/errors.rb +0 -70
  74. data/lib/robot_lab/history/active_record_adapter.rb +0 -146
  75. data/lib/robot_lab/history/config.rb +0 -115
  76. data/lib/robot_lab/history/thread_manager.rb +0 -93
  77. data/lib/robot_lab/robotic_model.rb +0 -324
@@ -1,314 +1,360 @@
1
1
  # Memory
2
2
 
3
- Namespaced key-value store for sharing data between robots.
3
+ Reactive key-value store for sharing data between robots.
4
4
 
5
5
  ## Class: `RobotLab::Memory`
6
6
 
7
7
  ```ruby
8
- memory = state.memory
8
+ memory = robot.memory
9
9
 
10
- memory.remember("key", "value")
11
- value = memory.recall("key")
10
+ memory.set(:key, "value")
11
+ value = memory.get(:key)
12
12
  ```
13
13
 
14
14
  ## Constants
15
15
 
16
- ### SHARED_NAMESPACE
16
+ ### RESERVED_KEYS
17
17
 
18
18
  ```ruby
19
- Memory::SHARED_NAMESPACE # => "SHARED"
19
+ Memory::RESERVED_KEYS # => [:data, :results, :messages, :session_id, :cache]
20
20
  ```
21
21
 
22
- Conventional namespace for cross-robot data.
22
+ Reserved keys with special accessors and behavior.
23
23
 
24
24
  ## Constructor
25
25
 
26
26
  ```ruby
27
- memory = Memory.new(initial_data = {})
27
+ memory = Memory.new(
28
+ data: {},
29
+ results: [],
30
+ messages: [],
31
+ session_id: nil,
32
+ backend: :auto,
33
+ enable_cache: true,
34
+ network_name: nil
35
+ )
36
+ ```
37
+
38
+ **Parameters:**
39
+
40
+ | Name | Type | Default | Description |
41
+ |------|------|---------|-------------|
42
+ | `data` | `Hash` | `{}` | Initial runtime data |
43
+ | `results` | `Array` | `[]` | Pre-loaded robot results |
44
+ | `messages` | `Array` | `[]` | Pre-loaded conversation messages |
45
+ | `session_id` | `String, nil` | `nil` | Session identifier |
46
+ | `backend` | `Symbol` | `:auto` | Storage backend (`:auto`, `:redis`, `:hash`) |
47
+ | `enable_cache` | `Boolean` | `true` | Whether to enable semantic caching |
48
+ | `network_name` | `String, nil` | `nil` | Network this memory belongs to |
49
+
50
+ ## Factory Method
51
+
52
+ ```ruby
53
+ memory = RobotLab.create_memory(data: { user_id: 123 })
28
54
  ```
29
55
 
30
56
  ## Methods
31
57
 
32
- ### remember
58
+ ### set
33
59
 
34
60
  ```ruby
35
- memory.remember(key, value)
61
+ memory.set(:key, value)
36
62
  ```
37
63
 
38
- Store a value.
64
+ Store a value and notify subscribers asynchronously.
39
65
 
40
66
  **Parameters:**
41
67
 
42
68
  | Name | Type | Description |
43
69
  |------|------|-------------|
44
- | `key` | `String`, `Symbol` | Storage key |
70
+ | `key` | `Symbol`, `String` | Storage key |
45
71
  | `value` | `Object` | Value to store |
46
72
 
47
- ### recall
73
+ ### get
48
74
 
49
75
  ```ruby
50
- memory.recall(key) # => Object | nil
76
+ memory.get(:key) # => value or nil
77
+ memory.get(:key, wait: true) # Block until available
78
+ memory.get(:key, wait: 30) # Block up to 30 seconds
79
+ memory.get(:a, :b, :c, wait: 60) # Multiple keys, returns Hash
51
80
  ```
52
81
 
53
- Retrieve a value.
82
+ Retrieve one or more values, optionally waiting until they exist.
54
83
 
55
84
  **Parameters:**
56
85
 
57
86
  | Name | Type | Description |
58
87
  |------|------|-------------|
59
- | `key` | `String`, `Symbol` | Storage key |
88
+ | `keys` | `Symbol`, `String` | One or more keys to retrieve |
89
+ | `wait` | `Boolean`, `Numeric` | `false`: immediate, `true`: block, `Numeric`: timeout |
60
90
 
61
- **Returns:** Stored value or `nil`.
91
+ **Returns:** Single value for one key, `Hash` for multiple keys.
62
92
 
63
- ### exists?
93
+ **Raises:** `AwaitTimeout` if timeout expires.
94
+
95
+ ### key?
64
96
 
65
97
  ```ruby
66
- memory.exists?(key) # => Boolean
98
+ memory.key?(:key) # => Boolean
67
99
  ```
68
100
 
69
- Check if key exists.
101
+ Check if key exists. Aliases: `has_key?`, `include?`.
70
102
 
71
- ### forget
103
+ ### delete
72
104
 
73
105
  ```ruby
74
- memory.forget(key) # => Object | nil
106
+ memory.delete(:key) # => deleted value
75
107
  ```
76
108
 
77
- Remove a key, returns the value.
109
+ Remove a key. Cannot delete reserved keys.
78
110
 
79
- ### all
111
+ ### keys
80
112
 
81
113
  ```ruby
82
- memory.all # => Hash
114
+ memory.keys # => Array<Symbol>
83
115
  ```
84
116
 
85
- Get all stored data.
117
+ Get all non-reserved keys.
86
118
 
87
- ### namespaces
119
+ ### clear
88
120
 
89
121
  ```ruby
90
- memory.namespaces # => Array<String>
122
+ memory.clear
91
123
  ```
92
124
 
93
- List all namespaces.
125
+ Clear all non-reserved keys.
94
126
 
95
- ### clear
127
+ ### reset
96
128
 
97
129
  ```ruby
98
- memory.clear
130
+ memory.reset
99
131
  ```
100
132
 
101
- Clear all data in current scope.
133
+ Reset memory to initial state (clears everything including reserved keys, preserves cache).
102
134
 
103
- ### clear_all
135
+ ### subscribe
104
136
 
105
137
  ```ruby
106
- memory.clear_all
138
+ sub_id = memory.subscribe(:key1, :key2) do |change|
139
+ puts "#{change.key} changed: #{change.value}"
140
+ end
107
141
  ```
108
142
 
109
- Clear all data globally.
143
+ Subscribe to changes on one or more keys. Callback receives a `MemoryChange` object.
110
144
 
111
- ### search
145
+ **MemoryChange attributes:**
146
+
147
+ | Attribute | Type | Description |
148
+ |-----------|------|-------------|
149
+ | `key` | `Symbol` | The changed key |
150
+ | `value` | `Object` | New value |
151
+ | `previous` | `Object` | Previous value |
152
+ | `writer` | `String, nil` | Name of robot that wrote |
153
+ | `network_name` | `String, nil` | Network name |
154
+ | `timestamp` | `Time` | When the change occurred |
155
+ | `created?` | `Boolean` | Previous was nil |
156
+ | `updated?` | `Boolean` | Previous was not nil |
157
+ | `deleted?` | `Boolean` | New value is nil |
158
+
159
+ ### subscribe_pattern
112
160
 
113
161
  ```ruby
114
- memory.search(pattern) # => Hash
162
+ sub_id = memory.subscribe_pattern("analysis:*") do |change|
163
+ puts "Analysis key #{change.key} updated"
164
+ end
115
165
  ```
116
166
 
117
- Find keys matching pattern.
167
+ Subscribe to keys matching a glob pattern (`*` and `?` supported).
118
168
 
119
- **Parameters:**
169
+ ### unsubscribe
120
170
 
121
- | Name | Type | Description |
122
- |------|------|-------------|
123
- | `pattern` | `String` | Glob pattern (e.g., "user:*") |
171
+ ```ruby
172
+ memory.unsubscribe(sub_id) # => Boolean
173
+ ```
174
+
175
+ Remove a subscription by its ID.
176
+
177
+ ### merge!
178
+
179
+ ```ruby
180
+ memory.merge!(key1: "value1", key2: "value2")
181
+ ```
182
+
183
+ Merge multiple key-value pairs into memory.
184
+
185
+ ## Reserved Key Accessors
186
+
187
+ ### data
188
+
189
+ ```ruby
190
+ memory.data # => StateProxy
191
+ memory.data[:user_id] # Hash access
192
+ memory.data.user_id # Method access
193
+ memory.data[:status] = "active"
194
+ ```
195
+
196
+ Runtime data accessed through `StateProxy` for method-style access.
124
197
 
125
- ### stats
198
+ ### results
126
199
 
127
200
  ```ruby
128
- memory.stats # => Hash
201
+ memory.results # => Array<RobotResult>
129
202
  ```
130
203
 
131
- Get memory statistics.
204
+ Accumulated robot results (returns a copy).
132
205
 
133
- **Returns:**
206
+ ### messages
134
207
 
135
208
  ```ruby
136
- {
137
- total_keys: 15,
138
- namespaces: ["user", "session"]
139
- }
209
+ memory.messages # => Array<Message>
140
210
  ```
141
211
 
142
- ### scoped
212
+ Conversation messages (returns a copy).
213
+
214
+ ### session_id
143
215
 
144
216
  ```ruby
145
- scoped_memory = memory.scoped(namespace) # => ScopedMemory
217
+ memory.session_id # => String | nil
218
+ memory.session_id = "abc" # Set session identifier
146
219
  ```
147
220
 
148
- Create a scoped view.
221
+ ### cache
149
222
 
150
- ## ScopedMemory
223
+ ```ruby
224
+ memory.cache # => RubyLLM::SemanticCache
225
+ ```
151
226
 
152
- Scoped view with automatic key prefixing.
227
+ Semantic cache module (read-only after initialization).
153
228
 
154
- ### Methods
229
+ ## Serialization
155
230
 
156
- All Memory methods are available:
231
+ ### to_h
157
232
 
158
233
  ```ruby
159
- scoped = memory.scoped("user:123")
234
+ memory.to_h
235
+ # => { data: {...}, results: [...], messages: [...], session_id: "...", custom: {...} }
236
+ ```
237
+
238
+ ### to_json
160
239
 
161
- scoped.remember("name", "Alice") # Key: "user:123:name"
162
- scoped.recall("name") # => "Alice"
163
- scoped.exists?("name") # => true
164
- scoped.forget("name")
165
- scoped.all # Only "user:123:*" keys
166
- scoped.clear # Clear only this scope
240
+ ```ruby
241
+ memory.to_json # => String
167
242
  ```
168
243
 
169
- ### Nested Scopes
244
+ ### from_hash
170
245
 
171
246
  ```ruby
172
- user = memory.scoped("user:123")
173
- prefs = user.scoped("preferences")
247
+ memory = Memory.from_hash(hash)
248
+ ```
249
+
250
+ Reconstruct memory from a hash.
251
+
252
+ ### clone
174
253
 
175
- prefs.remember("theme", "dark")
176
- # Full key: "user:123:preferences:theme"
254
+ ```ruby
255
+ new_memory = memory.clone
177
256
  ```
178
257
 
258
+ Deep copy with fresh subscriptions (cache and network_name preserved).
259
+
179
260
  ## Examples
180
261
 
181
262
  ### Basic Usage
182
263
 
183
264
  ```ruby
184
- state.memory.remember("user_name", "Alice")
185
- state.memory.remember("order_count", 5)
265
+ robot.memory.set(:user_name, "Alice")
266
+ robot.memory.set(:order_count, 5)
267
+
268
+ name = robot.memory.get(:user_name) # => "Alice"
269
+ count = robot.memory.get(:order_count) # => 5
270
+ ```
186
271
 
187
- name = state.memory.recall("user_name") # => "Alice"
188
- count = state.memory.recall("order_count") # => 5
272
+ ### Bracket Access
273
+
274
+ ```ruby
275
+ robot.memory[:user_id] = 123
276
+ robot.memory[:user_id] # => 123
189
277
  ```
190
278
 
191
279
  ### Storing Objects
192
280
 
193
281
  ```ruby
194
- state.memory.remember("user", {
282
+ robot.memory.set(:user, {
195
283
  id: 123,
196
284
  name: "Alice",
197
285
  plan: "pro"
198
286
  })
199
287
 
200
- user = state.memory.recall("user")
288
+ user = robot.memory.get(:user)
201
289
  user[:plan] # => "pro"
202
290
  ```
203
291
 
204
- ### Scoped Organization
292
+ ### Blocking Reads (Network Parallel Execution)
205
293
 
206
294
  ```ruby
207
- # User-specific data
208
- user = state.memory.scoped("user:#{user_id}")
209
- user.remember("last_login", Time.now)
210
- user.remember("preferences", { theme: "dark" })
295
+ # In robot A (writer)
296
+ network.memory.set(:sentiment, { score: 0.8, confidence: 0.95 })
211
297
 
212
- # Session-specific data
213
- session = state.memory.scoped("session:#{session_id}")
214
- session.remember("page_views", 0)
298
+ # In robot B (reader, may run concurrently)
299
+ result = network.memory.get(:sentiment, wait: true) # Block indefinitely
300
+ result = network.memory.get(:sentiment, wait: 30) # Block up to 30s
215
301
 
216
- # Temporary working data
217
- temp = state.memory.scoped("temp")
218
- temp.remember("intermediate_result", calculation)
302
+ # Multiple keys with timeout
303
+ results = network.memory.get(:sentiment, :entities, :keywords, wait: 60)
304
+ # => { sentiment: {...}, entities: [...], keywords: [...] }
219
305
  ```
220
306
 
221
- ### Cross-Robot Communication
307
+ ### Reactive Subscriptions
222
308
 
223
309
  ```ruby
224
- # In classifier robot
225
- state.memory.remember("SHARED:intent", "billing")
226
- state.memory.remember("SHARED:entities", ["order", "refund"])
227
-
228
- # In handler robot
229
- intent = state.memory.recall("SHARED:intent")
230
- entities = state.memory.recall("SHARED:entities")
231
- ```
232
-
233
- ### In Tool Handlers
234
-
235
- ```ruby
236
- tool :update_preference do
237
- handler do |key:, value:, state:, **_|
238
- prefs = state.memory.scoped("preferences")
239
- old_value = prefs.recall(key)
240
- prefs.remember(key, value)
241
-
242
- {
243
- success: true,
244
- key: key,
245
- old_value: old_value,
246
- new_value: value
247
- }
248
- end
310
+ # Subscribe to a key
311
+ memory.subscribe(:raw_data) do |change|
312
+ enriched = enrich(change.value)
313
+ memory.set(:enriched, enriched)
249
314
  end
250
- ```
251
-
252
- ### Search and Iteration
253
315
 
254
- ```ruby
255
- # Find all user keys
256
- user_data = state.memory.search("user:*")
257
- # => { "user:123:name" => "Alice", "user:123:email" => "..." }
258
-
259
- # Process all keys
260
- state.memory.all.each do |key, value|
261
- puts "#{key}: #{value}"
316
+ # Subscribe with pattern
317
+ memory.subscribe_pattern("user:*") do |change|
318
+ puts "User key #{change.key} updated by #{change.writer}"
262
319
  end
263
320
  ```
264
321
 
265
- ### Cleanup
322
+ ### Cross-Robot Communication via Network Memory
266
323
 
267
324
  ```ruby
268
- # Clear temporary data
269
- state.memory.scoped("temp").clear
270
-
271
- # Clear specific namespace
272
- state.memory.scoped("cache").clear
325
+ # In classifier robot
326
+ network.memory.set(:intent, "billing")
327
+ network.memory.set(:entities, ["order", "refund"])
273
328
 
274
- # Clear everything
275
- state.memory.clear_all
329
+ # In handler robot
330
+ intent = network.memory.get(:intent)
331
+ entities = network.memory.get(:entities)
276
332
  ```
277
333
 
278
- ### Caching Pattern
334
+ ### Data Proxy
279
335
 
280
336
  ```ruby
281
- def cached_fetch(state, key, &block)
282
- cache = state.memory.scoped("cache")
283
- cached = cache.recall(key)
284
- return cached if cached
285
-
286
- result = block.call
287
- cache.remember(key, result)
288
- result
289
- end
337
+ memory = RobotLab.create_memory(
338
+ data: { user: { name: "Alice", plan: "pro" } }
339
+ )
290
340
 
291
- # Usage
292
- data = cached_fetch(state, "expensive:#{id}") do
293
- ExpensiveService.fetch(id)
294
- end
341
+ memory.data[:user][:name] # => "Alice"
342
+ memory.data.to_h # => { user: { name: "Alice", plan: "pro" } }
295
343
  ```
296
344
 
297
- ### Accumulating Results
345
+ ### Serialization
298
346
 
299
347
  ```ruby
300
- # In each robot, accumulate findings
301
- findings = state.memory.recall("findings") || []
302
- findings << { robot: robot.name, finding: new_finding }
303
- state.memory.remember("findings", findings)
348
+ # Save memory
349
+ json = memory.to_json
350
+ File.write("memory.json", json)
304
351
 
305
- # In final robot, aggregate
306
- all_findings = state.memory.recall("findings")
307
- summary = all_findings.group_by { |f| f[:robot] }
352
+ # Restore memory
353
+ data = JSON.parse(File.read("memory.json"))
354
+ memory = Memory.from_hash(data)
308
355
  ```
309
356
 
310
357
  ## See Also
311
358
 
312
359
  - [Memory Guide](../../guides/memory.md)
313
- - [State](state.md)
314
360
  - [State Management Architecture](../../architecture/state-management.md)
@@ -5,7 +5,7 @@ Orchestrates multiple robots using SimpleFlow pipelines with DAG-based execution
5
5
  ## Class: `RobotLab::Network`
6
6
 
7
7
  ```ruby
8
- network = RobotLab.create_network(name: "support") do
8
+ network = RobotLab.create_network(name: "support", config: config) do
9
9
  task :classifier, classifier_robot, depends_on: :none
10
10
  task :billing, billing_robot, depends_on: :optional
11
11
  end
@@ -29,6 +29,14 @@ network.robots # => Hash<String, Robot>
29
29
 
30
30
  Hash of robots keyed by name.
31
31
 
32
+ ### config
33
+
34
+ ```ruby
35
+ network.config # => RunConfig
36
+ ```
37
+
38
+ Shared operational defaults for all robots in the network. Passed to robots during `run()` so they can inherit network-wide LLM settings. See [RunConfig](../../getting-started/configuration.md#runconfig-shared-operational-defaults).
39
+
32
40
  ### pipeline
33
41
 
34
42
  ```ruby
@@ -64,7 +72,7 @@ Execute the network pipeline.
64
72
  ### task
65
73
 
66
74
  ```ruby
67
- network.task(name, robot, context: {}, mcp: :none, tools: :none, memory: nil, depends_on: :none)
75
+ network.task(name, robot, context: {}, mcp: :none, tools: :none, memory: nil, config: nil, depends_on: :none)
68
76
  # => self
69
77
  ```
70
78
 
@@ -80,6 +88,7 @@ Add a task to the pipeline with optional per-task configuration.
80
88
  | `mcp` | `:none`, Array | MCP server config (`:none` or array of servers) |
81
89
  | `tools` | `:none`, Array | Tools config (`:none` or array of tools) |
82
90
  | `memory` | `Memory`, `nil` | Task-specific memory |
91
+ | `config` | `RunConfig`, `nil` | Per-task config (merged on top of network's RunConfig) |
83
92
  | `depends_on` | `:none`, `Array<Symbol>`, `:optional` | Task dependencies |
84
93
 
85
94
  **Dependency Types:**
@@ -153,7 +162,8 @@ Hash representation of network configuration.
153
162
  name: "support",
154
163
  robots: ["classifier", "billing", "technical"],
155
164
  tasks: ["classifier", "billing", "technical"],
156
- optional_tasks: [:billing, :technical]
165
+ optional_tasks: [:billing, :technical],
166
+ config: { model: "claude-sonnet-4", temperature: 0.7 } # if set
157
167
  }
158
168
  ```
159
169