robot_lab 0.0.1 → 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 (187) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/deploy-github-pages.yml +9 -9
  3. data/.irbrc +6 -0
  4. data/CHANGELOG.md +140 -0
  5. data/README.md +263 -48
  6. data/Rakefile +71 -1
  7. data/docs/api/core/index.md +53 -46
  8. data/docs/api/core/memory.md +200 -154
  9. data/docs/api/core/network.md +13 -3
  10. data/docs/api/core/robot.md +490 -130
  11. data/docs/api/core/state.md +55 -73
  12. data/docs/api/core/tool.md +205 -209
  13. data/docs/api/index.md +7 -28
  14. data/docs/api/mcp/client.md +119 -48
  15. data/docs/api/mcp/index.md +75 -60
  16. data/docs/api/mcp/server.md +120 -136
  17. data/docs/api/mcp/transports.md +172 -184
  18. data/docs/api/messages/index.md +35 -20
  19. data/docs/api/messages/text-message.md +67 -21
  20. data/docs/api/messages/tool-call-message.md +80 -41
  21. data/docs/api/messages/tool-result-message.md +119 -50
  22. data/docs/api/messages/user-message.md +48 -24
  23. data/docs/api/streaming/context.md +157 -74
  24. data/docs/api/streaming/events.md +114 -166
  25. data/docs/api/streaming/index.md +74 -72
  26. data/docs/architecture/core-concepts.md +360 -116
  27. data/docs/architecture/index.md +97 -59
  28. data/docs/architecture/message-flow.md +138 -129
  29. data/docs/architecture/network-orchestration.md +197 -50
  30. data/docs/architecture/robot-execution.md +199 -146
  31. data/docs/architecture/state-management.md +255 -187
  32. data/docs/concepts.md +311 -49
  33. data/docs/examples/basic-chat.md +89 -77
  34. data/docs/examples/index.md +222 -47
  35. data/docs/examples/mcp-server.md +207 -203
  36. data/docs/examples/multi-robot-network.md +129 -35
  37. data/docs/examples/rails-application.md +159 -160
  38. data/docs/examples/tool-usage.md +295 -204
  39. data/docs/getting-started/configuration.md +347 -154
  40. data/docs/getting-started/index.md +1 -1
  41. data/docs/getting-started/installation.md +22 -13
  42. data/docs/getting-started/quick-start.md +166 -121
  43. data/docs/guides/building-robots.md +418 -212
  44. data/docs/guides/creating-networks.md +143 -24
  45. data/docs/guides/index.md +0 -5
  46. data/docs/guides/mcp-integration.md +152 -113
  47. data/docs/guides/memory.md +220 -164
  48. data/docs/guides/rails-integration.md +244 -162
  49. data/docs/guides/streaming.md +137 -187
  50. data/docs/guides/using-tools.md +259 -212
  51. data/docs/index.md +46 -41
  52. data/examples/01_simple_robot.rb +6 -9
  53. data/examples/02_tools.rb +6 -9
  54. data/examples/03_network.rb +19 -17
  55. data/examples/04_mcp.rb +5 -8
  56. data/examples/05_streaming.rb +5 -8
  57. data/examples/06_prompt_templates.rb +42 -37
  58. data/examples/07_network_memory.rb +13 -14
  59. data/examples/08_llm_config.rb +169 -0
  60. data/examples/09_chaining.rb +262 -0
  61. data/examples/10_memory.rb +331 -0
  62. data/examples/11_network_introspection.rb +253 -0
  63. data/examples/12_message_bus.rb +74 -0
  64. data/examples/13_spawn.rb +90 -0
  65. data/examples/14_rusty_circuit/comic.rb +143 -0
  66. data/examples/14_rusty_circuit/display.rb +203 -0
  67. data/examples/14_rusty_circuit/heckler.rb +63 -0
  68. data/examples/14_rusty_circuit/open_mic.rb +123 -0
  69. data/examples/14_rusty_circuit/prompts/open_mic_comic.md +20 -0
  70. data/examples/14_rusty_circuit/prompts/open_mic_heckler.md +23 -0
  71. data/examples/14_rusty_circuit/prompts/open_mic_scout.md +20 -0
  72. data/examples/14_rusty_circuit/scout.rb +156 -0
  73. data/examples/14_rusty_circuit/scout_notes.md +89 -0
  74. data/examples/14_rusty_circuit/show.log +234 -0
  75. data/examples/15_memory_network_and_bus/editor_in_chief.rb +24 -0
  76. data/examples/15_memory_network_and_bus/editorial_pipeline.rb +206 -0
  77. data/examples/15_memory_network_and_bus/linux_writer.rb +80 -0
  78. data/examples/15_memory_network_and_bus/os_editor.rb +46 -0
  79. data/examples/15_memory_network_and_bus/os_writer.rb +46 -0
  80. data/examples/15_memory_network_and_bus/output/combined_article.md +13 -0
  81. data/examples/15_memory_network_and_bus/output/final_article.md +15 -0
  82. data/examples/15_memory_network_and_bus/output/linux_draft.md +5 -0
  83. data/examples/15_memory_network_and_bus/output/mac_draft.md +7 -0
  84. data/examples/15_memory_network_and_bus/output/memory.json +13 -0
  85. data/examples/15_memory_network_and_bus/output/revision_1.md +19 -0
  86. data/examples/15_memory_network_and_bus/output/revision_2.md +15 -0
  87. data/examples/15_memory_network_and_bus/output/windows_draft.md +7 -0
  88. data/examples/15_memory_network_and_bus/prompts/os_advocate.md +13 -0
  89. data/examples/15_memory_network_and_bus/prompts/os_chief.md +13 -0
  90. data/examples/15_memory_network_and_bus/prompts/os_editor.md +13 -0
  91. data/examples/16_writers_room/display.rb +158 -0
  92. data/examples/16_writers_room/output/.gitignore +2 -0
  93. data/examples/16_writers_room/output/opus_001.md +263 -0
  94. data/examples/16_writers_room/output/opus_001_notes.log +470 -0
  95. data/examples/16_writers_room/prompts/writer.md +37 -0
  96. data/examples/16_writers_room/room.rb +150 -0
  97. data/examples/16_writers_room/tools.rb +162 -0
  98. data/examples/16_writers_room/writer.rb +121 -0
  99. data/examples/16_writers_room/writers_room.rb +162 -0
  100. data/examples/README.md +197 -0
  101. data/examples/prompts/{assistant/system.txt.erb → assistant.md} +3 -0
  102. data/examples/prompts/{billing/system.txt.erb → billing.md} +3 -0
  103. data/examples/prompts/{classifier/system.txt.erb → classifier.md} +3 -0
  104. data/examples/prompts/comedian.md +6 -0
  105. data/examples/prompts/comedy_critic.md +10 -0
  106. data/examples/prompts/configurable.md +9 -0
  107. data/examples/prompts/dispatcher.md +12 -0
  108. data/examples/prompts/{entity_extractor/system.txt.erb → entity_extractor.md} +3 -0
  109. data/examples/prompts/{escalation/system.txt.erb → escalation.md} +7 -0
  110. data/examples/prompts/frontmatter_mcp_test.md +9 -0
  111. data/examples/prompts/frontmatter_named_test.md +5 -0
  112. data/examples/prompts/frontmatter_tools_test.md +6 -0
  113. data/examples/prompts/{general/system.txt.erb → general.md} +3 -0
  114. data/examples/prompts/{github_assistant/system.txt.erb → github_assistant.md} +8 -0
  115. data/examples/prompts/{helper/system.txt.erb → helper.md} +3 -0
  116. data/examples/prompts/{keyword_extractor/system.txt.erb → keyword_extractor.md} +3 -0
  117. data/examples/prompts/llm_config_demo.md +20 -0
  118. data/examples/prompts/{order_support/system.txt.erb → order_support.md} +8 -0
  119. data/examples/prompts/os_advocate.md +13 -0
  120. data/examples/prompts/os_chief.md +13 -0
  121. data/examples/prompts/os_editor.md +13 -0
  122. data/examples/prompts/{product_support/system.txt.erb → product_support.md} +7 -0
  123. data/examples/prompts/{sentiment_analyzer/system.txt.erb → sentiment_analyzer.md} +3 -0
  124. data/examples/prompts/{synthesizer/system.txt.erb → synthesizer.md} +3 -0
  125. data/examples/prompts/{technical/system.txt.erb → technical.md} +3 -0
  126. data/examples/prompts/{triage/system.txt.erb → triage.md} +6 -0
  127. data/lib/generators/robot_lab/templates/initializer.rb.tt +0 -13
  128. data/lib/robot_lab/ask_user.rb +75 -0
  129. data/lib/robot_lab/config/defaults.yml +121 -0
  130. data/lib/robot_lab/config.rb +183 -0
  131. data/lib/robot_lab/error.rb +6 -0
  132. data/lib/robot_lab/mcp/client.rb +1 -1
  133. data/lib/robot_lab/memory.rb +10 -34
  134. data/lib/robot_lab/network.rb +13 -20
  135. data/lib/robot_lab/robot/bus_messaging.rb +239 -0
  136. data/lib/robot_lab/robot/mcp_management.rb +88 -0
  137. data/lib/robot_lab/robot/template_rendering.rb +130 -0
  138. data/lib/robot_lab/robot.rb +240 -330
  139. data/lib/robot_lab/robot_message.rb +44 -0
  140. data/lib/robot_lab/robot_result.rb +1 -0
  141. data/lib/robot_lab/run_config.rb +184 -0
  142. data/lib/robot_lab/state_proxy.rb +2 -12
  143. data/lib/robot_lab/streaming/context.rb +1 -1
  144. data/lib/robot_lab/task.rb +8 -1
  145. data/lib/robot_lab/tool.rb +108 -172
  146. data/lib/robot_lab/tool_config.rb +1 -1
  147. data/lib/robot_lab/tool_manifest.rb +2 -18
  148. data/lib/robot_lab/utils.rb +39 -0
  149. data/lib/robot_lab/version.rb +1 -1
  150. data/lib/robot_lab.rb +89 -57
  151. data/mkdocs.yml +0 -11
  152. metadata +121 -135
  153. data/docs/api/adapters/anthropic.md +0 -121
  154. data/docs/api/adapters/gemini.md +0 -133
  155. data/docs/api/adapters/index.md +0 -104
  156. data/docs/api/adapters/openai.md +0 -134
  157. data/docs/api/history/active-record-adapter.md +0 -195
  158. data/docs/api/history/config.md +0 -191
  159. data/docs/api/history/index.md +0 -132
  160. data/docs/api/history/thread-manager.md +0 -144
  161. data/docs/guides/history.md +0 -359
  162. data/examples/prompts/assistant/user.txt.erb +0 -1
  163. data/examples/prompts/billing/user.txt.erb +0 -1
  164. data/examples/prompts/classifier/user.txt.erb +0 -1
  165. data/examples/prompts/entity_extractor/user.txt.erb +0 -3
  166. data/examples/prompts/escalation/user.txt.erb +0 -34
  167. data/examples/prompts/general/user.txt.erb +0 -1
  168. data/examples/prompts/github_assistant/user.txt.erb +0 -1
  169. data/examples/prompts/helper/user.txt.erb +0 -1
  170. data/examples/prompts/keyword_extractor/user.txt.erb +0 -3
  171. data/examples/prompts/order_support/user.txt.erb +0 -22
  172. data/examples/prompts/product_support/user.txt.erb +0 -32
  173. data/examples/prompts/sentiment_analyzer/user.txt.erb +0 -3
  174. data/examples/prompts/synthesizer/user.txt.erb +0 -15
  175. data/examples/prompts/technical/user.txt.erb +0 -1
  176. data/examples/prompts/triage/user.txt.erb +0 -17
  177. data/lib/robot_lab/adapters/anthropic.rb +0 -163
  178. data/lib/robot_lab/adapters/base.rb +0 -85
  179. data/lib/robot_lab/adapters/gemini.rb +0 -193
  180. data/lib/robot_lab/adapters/openai.rb +0 -159
  181. data/lib/robot_lab/adapters/registry.rb +0 -81
  182. data/lib/robot_lab/configuration.rb +0 -143
  183. data/lib/robot_lab/errors.rb +0 -70
  184. data/lib/robot_lab/history/active_record_adapter.rb +0 -146
  185. data/lib/robot_lab/history/config.rb +0 -115
  186. data/lib/robot_lab/history/thread_manager.rb +0 -93
  187. data/lib/robot_lab/robotic_model.rb +0 -324
@@ -1,259 +1,315 @@
1
1
  # Memory System
2
2
 
3
- The memory system allows robots to share data within a network run.
3
+ The memory system provides key-value storage for robots, supporting both standalone and network execution modes.
4
4
 
5
5
  ## Overview
6
6
 
7
- Memory provides:
7
+ Memory is a reactive key-value store that provides:
8
8
 
9
- - Key-value storage accessible by all robots
10
- - Namespaced scopes for organization
11
- - Persistence within a single network run
9
+ - Key-value storage with `[]` and `[]=` accessors
10
+ - Reserved keys for structured data (`:data`, `:results`, `:messages`, `:session_id`, `:cache`)
11
+ - Reactive subscriptions and blocking reads for inter-robot communication
12
+ - Optional Redis backend for persistence
13
+ - Semantic caching via `RubyLLM::SemanticCache`
12
14
 
13
- ## Basic Usage
15
+ ## Standalone Robot Memory
16
+
17
+ Every robot has its own inherent memory that persists across runs:
18
+
19
+ ```ruby
20
+ robot = RobotLab.build(
21
+ name: "assistant",
22
+ system_prompt: "You are helpful."
23
+ )
24
+
25
+ # Memory persists across runs
26
+ robot.memory[:user_name] = "Alice"
27
+ robot.memory[:preferences] = { theme: "dark", language: "en" }
28
+
29
+ result = robot.run("Hello!")
30
+
31
+ # Read it back later
32
+ robot.memory[:user_name] # => "Alice"
33
+ robot.memory[:preferences] # => { theme: "dark", language: "en" }
34
+ ```
35
+
36
+ ## Basic Operations
14
37
 
15
38
  ### Store Values
16
39
 
17
40
  ```ruby
18
- state.memory.remember("user_name", "Alice")
19
- state.memory.remember("preferences", { theme: "dark", language: "en" })
41
+ robot.memory[:key] = "value"
42
+ robot.memory[:count] = 42
43
+ robot.memory[:config] = { timeout: 30, retries: 3 }
20
44
  ```
21
45
 
22
46
  ### Retrieve Values
23
47
 
24
48
  ```ruby
25
- name = state.memory.recall("user_name") # => "Alice"
26
- prefs = state.memory.recall("preferences") # => { theme: "dark", ... }
27
-
28
- # Returns nil if not found
29
- missing = state.memory.recall("unknown") # => nil
49
+ name = robot.memory[:user_name] # => "Alice"
50
+ missing = robot.memory[:unknown] # => nil
30
51
  ```
31
52
 
32
53
  ### Check Existence
33
54
 
34
55
  ```ruby
35
- state.memory.exists?("user_name") # => true
36
- state.memory.exists?("unknown") # => false
56
+ robot.memory.key?(:user_name) # => true
57
+ robot.memory.key?(:unknown) # => false
37
58
  ```
38
59
 
39
- ### Remove Values
60
+ ### Delete Values
40
61
 
41
62
  ```ruby
42
- state.memory.forget("user_name")
63
+ robot.memory.delete(:temp_data)
43
64
  ```
44
65
 
45
- ## Scoped Memory
66
+ ### List Keys
46
67
 
47
- Organize data with namespaces:
68
+ ```ruby
69
+ robot.memory.keys # => [:user_name, :preferences] (excludes reserved keys)
70
+ robot.memory.all_keys # => [:data, :results, :messages, :session_id, :cache, :user_name, ...]
71
+ ```
72
+
73
+ ### Merge Values
48
74
 
49
75
  ```ruby
50
- # Create a scoped view
51
- user_memory = state.memory.scoped("user:123")
76
+ robot.memory.merge!(user_id: 123, session: "abc")
77
+ ```
78
+
79
+ ## Reserved Keys
52
80
 
53
- # Operations are scoped
54
- user_memory.remember("name", "Alice")
55
- user_memory.remember("email", "alice@example.com")
81
+ Memory has reserved keys with special behavior:
56
82
 
57
- # Keys are prefixed
58
- state.memory.recall("user:123:name") # => "Alice"
83
+ | Key | Type | Description |
84
+ |-----|------|-------------|
85
+ | `:data` | Hash (StateProxy) | Runtime data with method-style access |
86
+ | `:results` | Array | Accumulated robot results |
87
+ | `:messages` | Array | Conversation history |
88
+ | `:session_id` | String | Session identifier for history persistence |
89
+ | `:cache` | SemanticCache | Semantic cache (read-only after init) |
59
90
 
60
- # Scoped recall
61
- user_memory.recall("name") # => "Alice"
91
+ ### The Data Hash
92
+
93
+ The `:data` key provides a `StateProxy` for method-style access:
94
+
95
+ ```ruby
96
+ robot.memory.data[:category] = "billing"
97
+ robot.memory.data.category # => "billing" (method-style access)
98
+ robot.memory.data.to_h # => { category: "billing" }
99
+ ```
100
+
101
+ ### Results and Messages
102
+
103
+ ```ruby
104
+ robot.memory.results # => Array of RobotResult objects
105
+ robot.memory.messages # => Array of Message objects
106
+ robot.memory.session_id # => "abc123" or nil
62
107
  ```
63
108
 
64
- ### Nested Scopes
109
+ ## Runtime Memory Injection
110
+
111
+ Pass memory values for a single run using the `memory:` keyword:
65
112
 
66
113
  ```ruby
67
- session = state.memory.scoped("session:abc")
68
- prefs = session.scoped("preferences")
114
+ # Inject a hash -- values are merged into the active memory
115
+ result = robot.run("What's my order status?", memory: { user_id: 123, order_id: "ORD-456" })
69
116
 
70
- prefs.remember("theme", "dark")
71
- # Full key: "session:abc:preferences:theme"
117
+ # The robot's memory now contains those keys
118
+ robot.memory[:user_id] # => 123
119
+ robot.memory[:order_id] # => "ORD-456"
72
120
  ```
73
121
 
74
- ## Memory Operations
122
+ You can also pass a full `Memory` object to replace the active memory for that run:
123
+
124
+ ```ruby
125
+ custom_memory = RobotLab.create_memory(data: { user_id: 123 })
126
+ custom_memory[:context] = "billing inquiry"
75
127
 
76
- ### List All Keys
128
+ result = robot.run("Help me", memory: custom_memory)
129
+ ```
130
+
131
+ ## Resetting Memory
132
+
133
+ Clear a robot's memory back to its initial state:
77
134
 
78
135
  ```ruby
79
- state.memory.all
80
- # => {
81
- # "user_name" => "Alice",
82
- # "user:123:email" => "alice@example.com",
83
- # ...
84
- # }
136
+ robot.reset_memory
137
+ robot.memory.keys # => [] (custom keys cleared, reserved keys reset)
85
138
  ```
86
139
 
87
- ### List Namespaces
140
+ You can also clear just the custom keys without resetting reserved keys:
88
141
 
89
142
  ```ruby
90
- state.memory.namespaces
91
- # => ["user:123", "session:abc", ...]
143
+ robot.memory.clear # Clears non-reserved keys only
92
144
  ```
93
145
 
94
- ### Search by Pattern
146
+ ## Network Shared Memory
147
+
148
+ When robots run in a network, they share the network's memory instead of using their own inherent memory. This allows robots to communicate through shared state:
95
149
 
96
150
  ```ruby
97
- # Find keys matching pattern
98
- matches = state.memory.search("user:*")
99
- # => { "user:123:name" => "Alice", "user:123:email" => "..." }
151
+ network = RobotLab.create_network(name: "pipeline") do
152
+ task :analyzer, analyzer_robot, depends_on: :none
153
+ task :writer, writer_robot, depends_on: [:analyzer]
154
+ end
155
+
156
+ # All robots in the network share this memory
157
+ network.memory[:project] = "quarterly_report"
158
+
159
+ result = network.run(message: "Analyze sales data")
160
+
161
+ # After the run, shared memory contains values written by all robots
162
+ network.memory[:analysis_result] # Written by analyzer
163
+ network.memory[:draft] # Written by writer
100
164
  ```
101
165
 
102
- ### Statistics
166
+ ### Resetting Network Memory
103
167
 
104
168
  ```ruby
105
- state.memory.stats
106
- # => { total_keys: 15, namespaces: ["user:123", "session"] }
169
+ network.reset_memory # Clear shared memory between runs
107
170
  ```
108
171
 
109
- ### Clear Memory
172
+ ## Reactive Memory
173
+
174
+ Memory supports reactive features for concurrent robot execution.
175
+
176
+ ### Blocking Reads
177
+
178
+ Wait for a value to become available (useful in parallel pipelines):
110
179
 
111
180
  ```ruby
112
- # Clear a namespace
113
- state.memory.scoped("temp").clear
181
+ # In robot A (writer)
182
+ memory.set(:sentiment, { score: 0.8, confidence: 0.95 })
183
+
184
+ # In robot B (reader, may run concurrently)
185
+ result = memory.get(:sentiment, wait: true) # Blocks until available
186
+ result = memory.get(:sentiment, wait: 30) # Blocks up to 30 seconds
114
187
 
115
- # Clear all memory
116
- state.memory.clear_all
188
+ # Multiple keys
189
+ results = memory.get(:sentiment, :entities, :keywords, wait: 60)
190
+ # => { sentiment: {...}, entities: [...], keywords: [...] }
117
191
  ```
118
192
 
119
- ## Shared Namespace
193
+ ### Subscriptions
120
194
 
121
- The `SHARED` namespace is a convention for cross-robot data:
195
+ Subscribe to key changes with asynchronous callbacks:
122
196
 
123
197
  ```ruby
124
- # In first robot
125
- state.memory.remember("SHARED:context", important_data)
198
+ # Subscribe to a single key
199
+ memory.subscribe(:raw_data) do |change|
200
+ puts "#{change.key} changed by #{change.writer}"
201
+ puts "Old: #{change.previous}, New: #{change.value}"
202
+ end
203
+
204
+ # Subscribe to multiple keys
205
+ memory.subscribe(:sentiment, :entities) do |change|
206
+ update_dashboard(change.key, change.value)
207
+ end
126
208
 
127
- # In later robot
128
- context = state.memory.recall("SHARED:context")
209
+ # Pattern subscriptions (glob-style)
210
+ memory.subscribe_pattern("analysis:*") do |change|
211
+ puts "Analysis key #{change.key} updated"
212
+ end
129
213
  ```
130
214
 
131
- ### Using Shared Scope
215
+ ### Unsubscribe
132
216
 
133
217
  ```ruby
134
- shared = state.memory.scoped(RobotLab::Memory::SHARED_NAMESPACE)
135
- shared.remember("workflow_status", "in_progress")
218
+ sub_id = memory.subscribe(:status) { |c| puts c.value }
219
+ memory.unsubscribe(sub_id)
136
220
  ```
137
221
 
138
- ## In Tool Handlers
222
+ ## Creating Standalone Memory
139
223
 
140
- Access memory from tools:
224
+ Use the factory method for standalone memory objects:
141
225
 
142
226
  ```ruby
143
- tool :update_preference do
144
- description "Update user preference"
145
- parameter :key, type: :string, required: true
146
- parameter :value, type: :string, required: true
147
-
148
- handler do |key:, value:, state:, **_|
149
- prefs = state.memory.scoped("preferences")
150
- prefs.remember(key, value)
151
- { success: true, key: key, value: value }
152
- end
153
- end
227
+ memory = RobotLab.create_memory(
228
+ data: { user_id: 123, category: nil },
229
+ enable_cache: true
230
+ )
231
+
232
+ memory[:session_id] = "abc123"
233
+ memory[:custom_key] = "custom_value"
154
234
  ```
155
235
 
156
- ## In Routers
236
+ ## Serialization
157
237
 
158
- Use memory for routing decisions:
238
+ Memory can be exported and reconstructed:
159
239
 
160
240
  ```ruby
161
- router = ->(args) {
162
- case args.call_count
163
- when 0
164
- :classifier
165
- when 1
166
- # Read classification from memory
167
- intent = args.network.state.memory.recall("SHARED:intent")
168
- case intent
169
- when "billing" then :billing_agent
170
- when "technical" then :tech_agent
171
- else :general_agent
172
- end
173
- else
174
- nil
175
- end
176
- }
241
+ # Export to hash
242
+ hash = robot.memory.to_h
243
+ # => { data: {...}, results: [...], messages: [...], session_id: "...", custom: {...} }
244
+
245
+ # Export to JSON
246
+ json = robot.memory.to_json
247
+
248
+ # Reconstruct from hash
249
+ restored = RobotLab::Memory.from_hash(hash)
177
250
  ```
178
251
 
179
252
  ## Patterns
180
253
 
181
- ### Accumulating Data
254
+ ### Accumulating Data Across Robots
182
255
 
183
256
  ```ruby
184
- # In each robot
185
- def add_finding(state, finding)
186
- findings = state.memory.recall("findings") || []
257
+ # In each robot's processing
258
+ def accumulate_finding(memory, finding)
259
+ findings = memory[:findings] || []
187
260
  findings << finding
188
- state.memory.remember("findings", findings)
261
+ memory[:findings] = findings
189
262
  end
190
263
 
191
- # In final robot
192
- all_findings = state.memory.recall("findings")
264
+ # In the final robot
265
+ all_findings = memory[:findings]
193
266
  ```
194
267
 
195
268
  ### Tracking Progress
196
269
 
197
270
  ```ruby
198
- # Track workflow stages
199
- state.memory.remember("stage", "intake")
271
+ memory[:stage] = "intake"
200
272
  # ... processing ...
201
- state.memory.remember("stage", "analysis")
273
+ memory[:stage] = "analysis"
202
274
  # ... processing ...
203
- state.memory.remember("stage", "response")
275
+ memory[:stage] = "response"
204
276
  ```
205
277
 
206
278
  ### Caching Expensive Operations
207
279
 
208
280
  ```ruby
209
- tool :fetch_user do
210
- handler do |user_id:, state:, **_|
281
+ class FetchUser < RubyLLM::Tool
282
+ description "Fetch user details by ID"
283
+ param :user_id, type: :string, desc: "User ID"
284
+
285
+ def execute(user_id:)
211
286
  cache_key = "cache:user:#{user_id}"
212
287
 
213
- # Check cache
214
- cached = state.memory.recall(cache_key)
288
+ # Check robot's memory for cached value
289
+ # (In practice, you'd access memory through the robot's context)
290
+ cached = Thread.current[:robot_memory]&.[](cache_key.to_sym)
215
291
  return cached if cached
216
292
 
217
293
  # Fetch and cache
218
294
  user = User.find(user_id).to_h
219
- state.memory.remember(cache_key, user)
295
+ Thread.current[:robot_memory]&.[]=(cache_key.to_sym, user)
220
296
  user
221
297
  end
222
298
  end
223
299
  ```
224
300
 
225
- ### User Session Data
226
-
227
- ```ruby
228
- # Store session data
229
- session = state.memory.scoped("session:#{session_id}")
230
- session.remember("started_at", Time.now.iso8601)
231
- session.remember("page_views", 0)
232
-
233
- # Update during conversation
234
- views = session.recall("page_views") || 0
235
- session.remember("page_views", views + 1)
236
- ```
237
-
238
- ## Memory vs State.data
301
+ ### Semantic Caching
239
302
 
240
- | Feature | Memory | State.data |
241
- |---------|--------|------------|
242
- | Purpose | Robot-to-robot sharing | Input/output data |
243
- | Scope | Namespaced | Flat hash |
244
- | Typical Use | Intermediate results | User input, workflow config |
245
- | Persistence | Within run | Can be serialized |
303
+ Memory includes a semantic cache for LLM response caching:
246
304
 
247
305
  ```ruby
248
- # Use state.data for input configuration
249
- state = RobotLab.create_state(
250
- message: "Process order",
251
- data: { order_id: "123", priority: "high" }
252
- )
306
+ # Access the semantic cache
307
+ cache = robot.memory.cache # => RubyLLM::SemanticCache
253
308
 
254
- # Use memory for intermediate findings
255
- state.memory.remember("validation_result", { valid: true })
256
- state.memory.remember("processing_steps", ["validated", "charged"])
309
+ # Use it to cache semantically similar queries
310
+ response = cache.fetch("What is Ruby?") do
311
+ robot.run("What is Ruby?")
312
+ end
257
313
  ```
258
314
 
259
315
  ## Best Practices
@@ -262,48 +318,48 @@ state.memory.remember("processing_steps", ["validated", "charged"])
262
318
 
263
319
  ```ruby
264
320
  # Good
265
- state.memory.remember("classification:intent", "billing")
266
- state.memory.remember("user:123:last_order_id", "ord_456")
321
+ robot.memory[:classification_intent] = "billing"
322
+ robot.memory[:user_last_order_id] = "ord_456"
267
323
 
268
324
  # Bad
269
- state.memory.remember("x", "billing")
270
- state.memory.remember("temp1", "ord_456")
325
+ robot.memory[:x] = "billing"
326
+ robot.memory[:temp1] = "ord_456"
271
327
  ```
272
328
 
273
- ### 2. Scope Related Data
329
+ ### 2. Use Data Hash for Structured Runtime Input
274
330
 
275
331
  ```ruby
276
- # Good
277
- user = state.memory.scoped("user:#{user_id}")
278
- user.remember("name", name)
279
- user.remember("email", email)
280
- user.remember("plan", plan)
281
-
282
- # Less organized
283
- state.memory.remember("user_name", name)
284
- state.memory.remember("user_email", email)
285
- state.memory.remember("user_plan", plan)
332
+ memory = RobotLab.create_memory(
333
+ data: { order_id: "123", priority: "high", customer_tier: "gold" }
334
+ )
335
+
336
+ # Access via data proxy
337
+ memory.data.order_id # => "123"
338
+ memory.data.priority # => "high"
339
+ memory.data.customer_tier # => "gold"
286
340
  ```
287
341
 
288
- ### 3. Clean Up Temporary Data
342
+ ### 3. Clean Up Temporary Values
289
343
 
290
344
  ```ruby
291
- # At end of processing
292
- state.memory.scoped("temp").clear
345
+ # After processing is done
346
+ robot.memory.delete(:temp_calculation)
347
+ robot.memory.delete(:intermediate_result)
293
348
  ```
294
349
 
295
350
  ### 4. Document Memory Keys
296
351
 
297
352
  ```ruby
298
- # In your robot definitions, document expected keys
299
- # Memory keys used:
300
- # - SHARED:intent - Classification result
301
- # - SHARED:entities - Extracted entities
302
- # - user:{id}:* - User-specific data
353
+ # In your robot definitions, document expected keys:
354
+ #
355
+ # Memory keys used by this pipeline:
356
+ # - :intent - Classification result (set by classifier)
357
+ # - :entities - Extracted entities (set by entity_extractor)
358
+ # - :response - Final response draft (set by responder)
303
359
  ```
304
360
 
305
361
  ## Next Steps
306
362
 
307
- - [State Management](../architecture/state-management.md) - Full state details
308
363
  - [Building Robots](building-robots.md) - Using memory in robots
364
+ - [Creating Networks](creating-networks.md) - Shared memory in networks
309
365
  - [API Reference: Memory](../api/core/memory.md) - Complete API