robot_lab 0.3.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (256) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +12 -0
  3. data/README.md +1 -1
  4. data/lib/robot_lab/mcp/transports/streamable_http.rb +1 -1
  5. data/lib/robot_lab/version.rb +1 -1
  6. metadata +3 -253
  7. data/.architecture/AGENTS.md +0 -32
  8. data/.architecture/config.yml +0 -8
  9. data/.architecture/members.yml +0 -60
  10. data/.architecture/reviews/feature-free-will.md +0 -490
  11. data/.architecture/reviews/overall-codebase.md +0 -427
  12. data/.claude/settings.local.json +0 -57
  13. data/.codex/config.toml +0 -2
  14. data/.envrc +0 -6
  15. data/.github/workflows/deploy-github-pages.yml +0 -52
  16. data/.irbrc +0 -6
  17. data/.loki +0 -65
  18. data/.quality/reek_baseline.txt +0 -43
  19. data/.rubocop.yml +0 -10
  20. data/Archspec.rb +0 -44
  21. data/CLAUDE.md +0 -141
  22. data/COMMITS.md +0 -196
  23. data/Rakefile +0 -193
  24. data/_typos.toml +0 -21
  25. data/agent2agent_review.md +0 -192
  26. data/agentf_improvements.md +0 -253
  27. data/agents.md +0 -14
  28. data/docs/api/core/index.md +0 -147
  29. data/docs/api/core/memory.md +0 -578
  30. data/docs/api/core/network.md +0 -553
  31. data/docs/api/core/result.md +0 -211
  32. data/docs/api/core/robot.md +0 -1667
  33. data/docs/api/core/state.md +0 -145
  34. data/docs/api/core/tool.md +0 -520
  35. data/docs/api/errors.md +0 -323
  36. data/docs/api/hooks.md +0 -469
  37. data/docs/api/index.md +0 -137
  38. data/docs/api/mcp/client.md +0 -387
  39. data/docs/api/mcp/index.md +0 -267
  40. data/docs/api/mcp/server.md +0 -252
  41. data/docs/api/mcp/transports.md +0 -339
  42. data/docs/api/messages/index.md +0 -126
  43. data/docs/api/messages/text-message.md +0 -170
  44. data/docs/api/messages/tool-call-message.md +0 -206
  45. data/docs/api/messages/tool-result-message.md +0 -242
  46. data/docs/api/messages/user-message.md +0 -232
  47. data/docs/api/skills.md +0 -423
  48. data/docs/api/streaming/context.md +0 -280
  49. data/docs/api/streaming/events.md +0 -185
  50. data/docs/api/streaming/index.md +0 -156
  51. data/docs/api/support.md +0 -846
  52. data/docs/architecture/core-concepts.md +0 -575
  53. data/docs/architecture/index.md +0 -252
  54. data/docs/architecture/message-flow.md +0 -366
  55. data/docs/architecture/network-orchestration.md +0 -478
  56. data/docs/architecture/robot-execution.md +0 -396
  57. data/docs/architecture/state-management.md +0 -410
  58. data/docs/assets/css/custom.css +0 -56
  59. data/docs/assets/images/robot_lab.jpg +0 -0
  60. data/docs/concepts.md +0 -641
  61. data/docs/examples/basic-chat.md +0 -258
  62. data/docs/examples/index.md +0 -425
  63. data/docs/examples/mcp-server.md +0 -403
  64. data/docs/examples/multi-robot-network.md +0 -476
  65. data/docs/examples/tool-usage.md +0 -468
  66. data/docs/getting-started/configuration.md +0 -645
  67. data/docs/getting-started/index.md +0 -56
  68. data/docs/getting-started/installation.md +0 -197
  69. data/docs/getting-started/quick-start.md +0 -293
  70. data/docs/guides/building-robots.md +0 -1187
  71. data/docs/guides/creating-networks.md +0 -736
  72. data/docs/guides/hooks.md +0 -1206
  73. data/docs/guides/index.md +0 -78
  74. data/docs/guides/knowledge.md +0 -219
  75. data/docs/guides/mcp-integration.md +0 -740
  76. data/docs/guides/memory.md +0 -458
  77. data/docs/guides/observability.md +0 -733
  78. data/docs/guides/streaming.md +0 -323
  79. data/docs/guides/using-tools.md +0 -780
  80. data/docs/index.md +0 -280
  81. data/docs/superpowers/plans/2026-05-06-agentskills.md +0 -1303
  82. data/docs/superpowers/specs/2026-05-06-agentskills-design.md +0 -247
  83. data/examples/.envrc +0 -3
  84. data/examples/01_simple_robot.rb +0 -31
  85. data/examples/02_tools.rb +0 -109
  86. data/examples/03_network.rb +0 -118
  87. data/examples/04_mcp.rb +0 -217
  88. data/examples/05_streaming.rb +0 -137
  89. data/examples/06_prompt_templates.rb +0 -325
  90. data/examples/07_network_memory.rb +0 -313
  91. data/examples/08_llm_config.rb +0 -162
  92. data/examples/09_chaining.rb +0 -251
  93. data/examples/10_memory.rb +0 -315
  94. data/examples/11_network_introspection.rb +0 -239
  95. data/examples/12_message_bus.rb +0 -67
  96. data/examples/13_spawn.rb +0 -85
  97. data/examples/14_rusty_circuit/.envrc +0 -1
  98. data/examples/14_rusty_circuit/.gitignore +0 -1
  99. data/examples/14_rusty_circuit/README.md +0 -1
  100. data/examples/14_rusty_circuit/comic.rb +0 -149
  101. data/examples/14_rusty_circuit/display.rb +0 -217
  102. data/examples/14_rusty_circuit/heckler.rb +0 -65
  103. data/examples/14_rusty_circuit/open_mic.rb +0 -132
  104. data/examples/14_rusty_circuit/prompts/open_mic_comic.md +0 -20
  105. data/examples/14_rusty_circuit/prompts/open_mic_heckler.md +0 -23
  106. data/examples/14_rusty_circuit/prompts/open_mic_scout.md +0 -20
  107. data/examples/14_rusty_circuit/scout.rb +0 -165
  108. data/examples/14_rusty_circuit/show.log +0 -234
  109. data/examples/15_memory_network_and_bus/.envrc +0 -1
  110. data/examples/15_memory_network_and_bus/README.md +0 -66
  111. data/examples/15_memory_network_and_bus/editor_in_chief.rb +0 -24
  112. data/examples/15_memory_network_and_bus/editorial_pipeline.rb +0 -213
  113. data/examples/15_memory_network_and_bus/linux_writer.rb +0 -80
  114. data/examples/15_memory_network_and_bus/os_editor.rb +0 -48
  115. data/examples/15_memory_network_and_bus/os_writer.rb +0 -49
  116. data/examples/15_memory_network_and_bus/output/combined_article.md +0 -11
  117. data/examples/15_memory_network_and_bus/output/final_article.md +0 -8
  118. data/examples/15_memory_network_and_bus/output/linux_draft.md +0 -7
  119. data/examples/15_memory_network_and_bus/output/mac_draft.md +0 -7
  120. data/examples/15_memory_network_and_bus/output/memory.json +0 -13
  121. data/examples/15_memory_network_and_bus/output/revision_1.md +0 -29
  122. data/examples/15_memory_network_and_bus/output/revision_2.md +0 -50
  123. data/examples/15_memory_network_and_bus/output/revision_3.md +0 -8
  124. data/examples/15_memory_network_and_bus/output/windows_draft.md +0 -7
  125. data/examples/15_memory_network_and_bus/prompts/os_advocate.md +0 -13
  126. data/examples/15_memory_network_and_bus/prompts/os_chief.md +0 -13
  127. data/examples/15_memory_network_and_bus/prompts/os_editor.md +0 -13
  128. data/examples/16_writers_room/.envrc +0 -1
  129. data/examples/16_writers_room/display.rb +0 -158
  130. data/examples/16_writers_room/output/.gitignore +0 -4
  131. data/examples/16_writers_room/output/README.md +0 -69
  132. data/examples/16_writers_room/output/opus_001.md +0 -263
  133. data/examples/16_writers_room/output/opus_001_notes.log +0 -470
  134. data/examples/16_writers_room/output/opus_002.md +0 -245
  135. data/examples/16_writers_room/output/opus_002_notes.log +0 -546
  136. data/examples/16_writers_room/output/opus_002_screenplay.md +0 -7989
  137. data/examples/16_writers_room/output/opus_002_screenplay_notes.md +0 -993
  138. data/examples/16_writers_room/prompts/screenplay_writer.md +0 -66
  139. data/examples/16_writers_room/prompts/writer.md +0 -37
  140. data/examples/16_writers_room/room.rb +0 -186
  141. data/examples/16_writers_room/tools.rb +0 -173
  142. data/examples/16_writers_room/writer.rb +0 -121
  143. data/examples/16_writers_room/writers_room.rb +0 -256
  144. data/examples/17_skills.rb +0 -234
  145. data/examples/18_rails/.envrc +0 -3
  146. data/examples/18_rails/.gitignore +0 -5
  147. data/examples/18_rails/Gemfile +0 -11
  148. data/examples/18_rails/README.md +0 -67
  149. data/examples/18_rails/Rakefile +0 -4
  150. data/examples/18_rails/app/controllers/application_controller.rb +0 -4
  151. data/examples/18_rails/app/controllers/chat_controller.rb +0 -50
  152. data/examples/18_rails/app/jobs/application_job.rb +0 -4
  153. data/examples/18_rails/app/jobs/robot_run_job.rb +0 -25
  154. data/examples/18_rails/app/models/application_record.rb +0 -5
  155. data/examples/18_rails/app/models/robot_lab_result.rb +0 -36
  156. data/examples/18_rails/app/models/robot_lab_thread.rb +0 -23
  157. data/examples/18_rails/app/robots/chat_robot.rb +0 -25
  158. data/examples/18_rails/app/tools/time_tool.rb +0 -9
  159. data/examples/18_rails/app/views/chat/_user_message.html.erb +0 -1
  160. data/examples/18_rails/app/views/chat/index.html.erb +0 -67
  161. data/examples/18_rails/app/views/layouts/application.html.erb +0 -49
  162. data/examples/18_rails/bin/dev +0 -7
  163. data/examples/18_rails/bin/rails +0 -6
  164. data/examples/18_rails/bin/setup +0 -15
  165. data/examples/18_rails/config/application.rb +0 -33
  166. data/examples/18_rails/config/cable.yml +0 -2
  167. data/examples/18_rails/config/database.yml +0 -5
  168. data/examples/18_rails/config/environment.rb +0 -4
  169. data/examples/18_rails/config/initializers/robot_lab.rb +0 -11
  170. data/examples/18_rails/config/routes.rb +0 -6
  171. data/examples/18_rails/config.ru +0 -4
  172. data/examples/18_rails/db/migrate/001_create_robot_lab_tables.rb +0 -32
  173. data/examples/19_token_tracking.rb +0 -138
  174. data/examples/20_circuit_breaker.rb +0 -147
  175. data/examples/21_learning_loop.rb +0 -181
  176. data/examples/22_context_compression.rb +0 -172
  177. data/examples/23_convergence.rb +0 -133
  178. data/examples/24_structured_delegation.rb +0 -151
  179. data/examples/25_history_search/conversation.jsonl +0 -30
  180. data/examples/25_history_search.rb +0 -133
  181. data/examples/26_document_store/api_versioning_adr.md +0 -52
  182. data/examples/26_document_store/incident_postmortem.md +0 -46
  183. data/examples/26_document_store/postgres_runbook.md +0 -49
  184. data/examples/26_document_store/redis_caching_guide.md +0 -48
  185. data/examples/26_document_store/sidekiq_guide.md +0 -51
  186. data/examples/26_document_store.rb +0 -140
  187. data/examples/27_incident_response/README.md +0 -65
  188. data/examples/27_incident_response/incident_response.rb +0 -261
  189. data/examples/28_mcp_discovery.rb +0 -113
  190. data/examples/29_ractor_tools.rb +0 -240
  191. data/examples/30_ractor_network.rb +0 -252
  192. data/examples/31_launch_assessment.rb +0 -246
  193. data/examples/32_newsletter_reader.rb +0 -193
  194. data/examples/33_stock_generator.rb +0 -80
  195. data/examples/33_stock_predictor.rb +0 -327
  196. data/examples/34_agentskills.rb +0 -76
  197. data/examples/35_hooks.rb +0 -266
  198. data/examples/README.md +0 -550
  199. data/examples/common.rb +0 -180
  200. data/examples/prompts/assistant.md +0 -5
  201. data/examples/prompts/audit_trail.md +0 -8
  202. data/examples/prompts/billing.md +0 -10
  203. data/examples/prompts/classifier.md +0 -7
  204. data/examples/prompts/comedian.md +0 -6
  205. data/examples/prompts/comedy_critic.md +0 -10
  206. data/examples/prompts/configurable.md +0 -9
  207. data/examples/prompts/dispatcher.md +0 -12
  208. data/examples/prompts/entity_extractor.md +0 -14
  209. data/examples/prompts/escalation.md +0 -42
  210. data/examples/prompts/frontmatter_mcp_test.md +0 -9
  211. data/examples/prompts/frontmatter_named_test.md +0 -5
  212. data/examples/prompts/frontmatter_tools_test.md +0 -6
  213. data/examples/prompts/general.md +0 -7
  214. data/examples/prompts/github_assistant.md +0 -14
  215. data/examples/prompts/helper.md +0 -4
  216. data/examples/prompts/incident_responder.md +0 -18
  217. data/examples/prompts/keyword_extractor.md +0 -11
  218. data/examples/prompts/llm_config_demo.md +0 -20
  219. data/examples/prompts/order_support.md +0 -35
  220. data/examples/prompts/os_advocate.md +0 -13
  221. data/examples/prompts/os_chief.md +0 -13
  222. data/examples/prompts/os_editor.md +0 -13
  223. data/examples/prompts/parameterized_main_test.md +0 -6
  224. data/examples/prompts/pii_redactor.md +0 -7
  225. data/examples/prompts/product_support.md +0 -37
  226. data/examples/prompts/runbook_protocol.md +0 -12
  227. data/examples/prompts/sentiment_analyzer.md +0 -12
  228. data/examples/prompts/skill_a_test.md +0 -4
  229. data/examples/prompts/skill_b_test.md +0 -4
  230. data/examples/prompts/skill_config_test.md +0 -5
  231. data/examples/prompts/skill_cycle_a_test.md +0 -6
  232. data/examples/prompts/skill_cycle_b_test.md +0 -6
  233. data/examples/prompts/skill_description_test.md +0 -4
  234. data/examples/prompts/skill_leaf_test.md +0 -4
  235. data/examples/prompts/skill_nested_test.md +0 -6
  236. data/examples/prompts/skill_refs_main_test.md +0 -6
  237. data/examples/prompts/skill_self_ref_test.md +0 -6
  238. data/examples/prompts/skill_with_mcp_test.md +0 -9
  239. data/examples/prompts/skill_with_params_test.md +0 -6
  240. data/examples/prompts/skill_with_robot_name_test.md +0 -5
  241. data/examples/prompts/skill_with_tools_test.md +0 -6
  242. data/examples/prompts/sre_compliance.md +0 -10
  243. data/examples/prompts/structured_output.md +0 -7
  244. data/examples/prompts/synthesizer.md +0 -17
  245. data/examples/prompts/technical.md +0 -10
  246. data/examples/prompts/template_with_skills_test.md +0 -6
  247. data/examples/prompts/triage.md +0 -22
  248. data/examples/ruboruby.md +0 -423
  249. data/examples/run_all.rb +0 -60
  250. data/examples/xyzzy.rb +0 -97
  251. data/logfile +0 -8
  252. data/mkdocs.yml +0 -209
  253. data/robot_concurrency.md +0 -38
  254. data/simple_acp_review.md +0 -298
  255. data/temp.md +0 -6
  256. data/tool_manifest_plan.md +0 -155
@@ -1,458 +0,0 @@
1
- # Memory System
2
-
3
- The memory system provides key-value storage for robots, supporting both standalone and network execution modes.
4
-
5
- ## Overview
6
-
7
- Memory is a reactive key-value store that provides:
8
-
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` (optional ruby_llm-semantic_cache gem)
14
-
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
37
-
38
- ### Store Values
39
-
40
- ```ruby
41
- robot.memory[:key] = "value"
42
- robot.memory[:count] = 42
43
- robot.memory[:config] = { timeout: 30, retries: 3 }
44
- ```
45
-
46
- ### Retrieve Values
47
-
48
- ```ruby
49
- name = robot.memory[:user_name] # => "Alice"
50
- missing = robot.memory[:unknown] # => nil
51
- ```
52
-
53
- ### Check Existence
54
-
55
- ```ruby
56
- robot.memory.key?(:user_name) # => true
57
- robot.memory.key?(:unknown) # => false
58
- ```
59
-
60
- ### Delete Values
61
-
62
- ```ruby
63
- robot.memory.delete(:temp_data)
64
- ```
65
-
66
- ### List Keys
67
-
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
74
-
75
- ```ruby
76
- robot.memory.merge!(user_id: 123, session: "abc")
77
- ```
78
-
79
- ## Reserved Keys
80
-
81
- Memory has reserved keys with special behavior:
82
-
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` | `RubyLLM::SemanticCache` module, or `nil` | Semantic cache (read-only after init). Set at construction time; `nil` when built with `enable_cache: false` or when the optional gem is absent |
90
-
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
107
- ```
108
-
109
- ## Runtime Memory Injection
110
-
111
- Pass memory values for a single run using the `memory:` keyword:
112
-
113
- ```ruby
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" })
116
-
117
- # The robot's memory now contains those keys
118
- robot.memory[:user_id] # => 123
119
- robot.memory[:order_id] # => "ORD-456"
120
- ```
121
-
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"
127
-
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:
134
-
135
- ```ruby
136
- robot.reset_memory
137
- robot.memory.keys # => [] (custom keys cleared, reserved keys reset)
138
- ```
139
-
140
- You can also clear just the custom keys without resetting reserved keys:
141
-
142
- ```ruby
143
- robot.memory.clear # Clears non-reserved keys only
144
- ```
145
-
146
- > [!WARNING]
147
- > `reset_memory` resets **only the key-value store**. It does not touch the
148
- > robot's conversation history — the chat still holds every prior message, and
149
- > the LLM will still see them. Clearing the transcript is a separate call:
150
- >
151
- > ```ruby
152
- > robot.reset_memory # key-value store only
153
- > robot.clear_messages(keep_system: true) # conversation history only
154
- > ```
155
-
156
- ## Network Shared Memory
157
-
158
- 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:
159
-
160
- ```ruby
161
- network = RobotLab.create_network(name: "pipeline") do
162
- task :analyzer, analyzer_robot, depends_on: :none
163
- task :writer, writer_robot, depends_on: [:analyzer]
164
- end
165
-
166
- # All robots in the network share this memory
167
- network.memory[:project] = "quarterly_report"
168
-
169
- result = network.run(message: "Analyze sales data")
170
-
171
- # After the run, shared memory contains values written by all robots
172
- network.memory[:analysis_result] # Written by analyzer
173
- network.memory[:draft] # Written by writer
174
- ```
175
-
176
- ### Resetting Network Memory
177
-
178
- ```ruby
179
- network.reset_memory # Clear shared memory between runs
180
- ```
181
-
182
- ## Reactive Memory
183
-
184
- Memory supports reactive features for concurrent robot execution.
185
-
186
- ### Blocking Reads
187
-
188
- Wait for a value to become available (useful in parallel pipelines):
189
-
190
- ```ruby
191
- # In robot A (writer)
192
- memory.set(:sentiment, { score: 0.8, confidence: 0.95 })
193
-
194
- # In robot B (reader, may run concurrently)
195
- result = memory.get(:sentiment, wait: true) # Blocks until available
196
- result = memory.get(:sentiment, wait: 30) # Blocks up to 30 seconds
197
-
198
- # Multiple keys
199
- results = memory.get(:sentiment, :entities, :keywords, wait: 60)
200
- # => { sentiment: {...}, entities: [...], keywords: [...] }
201
- ```
202
-
203
- > [!WARNING]
204
- > A blocking `get` that expires **raises `RobotLab::AwaitTimeout`** — it does
205
- > not return `nil`. (A *non*-blocking `get`, the default, returns `nil` for a
206
- > missing key.) Wrap it if a missing value is survivable:
207
- >
208
- > ```ruby
209
- > begin
210
- > memory.get(:sentiment, wait: 30)
211
- > rescue RobotLab::AwaitTimeout => e
212
- > # => "Timeout waiting for :sentiment after 30 seconds"
213
- > nil
214
- > end
215
- > ```
216
-
217
- > [!CAUTION]
218
- > With multiple keys the timeout is applied **per missing key**, not to the call
219
- > as a whole. `memory.get(:a, :b, :c, wait: 30)` awaits the missing keys
220
- > sequentially, each with its own fresh 30-second budget.
221
- >
222
- > It does not, however, spend the whole 90 seconds before reporting: the first
223
- > key whose wait expires **raises `AwaitTimeout` immediately**, aborting the call
224
- > — so the keys that were already resolved are lost along with the ones not yet
225
- > attempted. The 90 seconds is the worst case only for a call that *succeeds*
226
- > (each key arriving just under its own deadline).
227
-
228
- Each blocking wait is backed by an `IO.pipe` pair (`Waiter` class). The waiting
229
- side calls `@read_io.wait_readable(timeout)`; `signal` writes one byte per
230
- waiting caller so every blocked waiter wakes exactly once. `wait_readable`
231
- yields to Ruby's Async fiber scheduler when one is installed — no mutex
232
- contention or spurious wakeups.
233
-
234
- ### Subscriptions
235
-
236
- Subscribe to key changes:
237
-
238
- ```ruby
239
- # Subscribe to a single key
240
- memory.subscribe(:raw_data) do |change|
241
- puts "#{change.key} changed by #{change.writer}"
242
- puts "Old: #{change.previous}, New: #{change.value}"
243
- end
244
-
245
- # Subscribe to multiple keys
246
- memory.subscribe(:sentiment, :entities) do |change|
247
- update_dashboard(change.key, change.value)
248
- end
249
-
250
- # Pattern subscriptions (glob-style)
251
- memory.subscribe_pattern("analysis:*") do |change|
252
- puts "Analysis key #{change.key} updated"
253
- end
254
- ```
255
-
256
- > [!IMPORTANT]
257
- > Subscription callbacks are dispatched through `Async { }`. Inside a running
258
- > Async reactor that defers them; **outside one — which is the normal case for
259
- > plain Ruby, Rails request threads, and tests — the block runs synchronously
260
- > on the writer's thread**, before `memory[:key] = value` returns:
261
- >
262
- > ```ruby
263
- > order = []
264
- > memory.subscribe(:k) { |c| order << "callback" }
265
- > order << "before-set"
266
- > memory[:k] = 1
267
- > order << "after-set"
268
- > order # => ["before-set", "callback", "after-set"]
269
- > ```
270
- >
271
- > Keep subscription callbacks fast, and never assume the writer has moved on by
272
- > the time your callback runs.
273
-
274
- ### Unsubscribe
275
-
276
- ```ruby
277
- sub_id = memory.subscribe(:status) { |c| puts c.value }
278
- memory.unsubscribe(sub_id)
279
- ```
280
-
281
- ## Creating Standalone Memory
282
-
283
- Use the factory method for standalone memory objects:
284
-
285
- ```ruby
286
- memory = RobotLab.create_memory(
287
- data: { user_id: 123, category: nil },
288
- enable_cache: true
289
- )
290
-
291
- memory[:session_id] = "abc123"
292
- memory[:custom_key] = "custom_value"
293
- ```
294
-
295
- ## Serialization
296
-
297
- Memory can be exported and reconstructed:
298
-
299
- ```ruby
300
- # Export to hash
301
- hash = robot.memory.to_h
302
- # => { data: {...}, results: [...], messages: [...], session_id: "...", custom: {...} }
303
- # to_h is compacted: nil entries are dropped, so an unset :session_id (and the
304
- # :cache key, which is never exported) simply will not appear.
305
-
306
- # Export to JSON
307
- json = robot.memory.to_json
308
-
309
- # Reconstruct from hash
310
- restored = RobotLab::Memory.from_hash(hash)
311
- ```
312
-
313
- ## Patterns
314
-
315
- ### Accumulating Data Across Robots
316
-
317
- ```ruby
318
- # In each robot's processing
319
- def accumulate_finding(memory, finding)
320
- findings = memory[:findings] || []
321
- findings << finding
322
- memory[:findings] = findings
323
- end
324
-
325
- # In the final robot
326
- all_findings = memory[:findings]
327
- ```
328
-
329
- ### Tracking Progress
330
-
331
- ```ruby
332
- memory[:stage] = "intake"
333
- # ... processing ...
334
- memory[:stage] = "analysis"
335
- # ... processing ...
336
- memory[:stage] = "response"
337
- ```
338
-
339
- ### Caching Expensive Operations
340
-
341
- A tool reaches memory through its owning robot. Subclass `RobotLab::Tool` (which
342
- has a `robot` accessor) and attach an **instance constructed with `robot:`** —
343
- that is the only supported way for tool code to read and write robot memory:
344
-
345
- ```ruby
346
- class FetchUser < RobotLab::Tool
347
- description "Fetch user details by ID"
348
- param :user_id, type: :string, desc: "User ID"
349
-
350
- def execute(user_id:)
351
- cache_key = :"cache:user:#{user_id}"
352
-
353
- cached = robot&.memory&.[](cache_key)
354
- return cached if cached
355
-
356
- user = User.find(user_id).to_h
357
- robot&.memory&.[]=(cache_key, user)
358
- user
359
- end
360
- end
361
-
362
- robot = RobotLab.build(name: "support", system_prompt: "...")
363
- robot.local_tools << FetchUser.new(robot: robot)
364
-
365
- robot.run("Look up user 42", tools: :inherit)
366
- ```
367
-
368
- > [!WARNING]
369
- > There is **no thread-local memory handle** in RobotLab — nothing anywhere in
370
- > the codebase ever assigns `Thread.current[:robot_memory]`. A tool that reads
371
- > it will always see `nil` and silently cache nothing. Go through `robot.memory`
372
- > as above, and remember that `FetchUser.new` without `robot:` leaves `robot`
373
- > `nil`.
374
-
375
- ### Semantic Caching
376
-
377
- Memory exposes a semantic cache for LLM response caching. It is on by default
378
- and becomes `nil` when you opt out with `enable_cache: false`:
379
-
380
- ```ruby
381
- RobotLab.create_memory.cache # => RubyLLM::SemanticCache
382
- RobotLab.create_memory(enable_cache: false).cache # => nil
383
-
384
- RobotLab.build(name: "x", system_prompt: "…").memory.cache
385
- # => RubyLLM::SemanticCache
386
- RobotLab.build(name: "x", system_prompt: "…", enable_cache: false).memory.cache
387
- # => nil
388
- ```
389
-
390
- Guard for `nil` in any code that might run against a cache-disabled memory.
391
-
392
- ```ruby
393
- # Access the semantic cache
394
- cache = robot.memory.cache
395
-
396
- # Use it to cache semantically similar queries
397
- response = cache.fetch("What is Ruby?") do
398
- robot.run("What is Ruby?")
399
- end
400
- ```
401
-
402
- > [!NOTE]
403
- > `memory.cache` is the `RubyLLM::SemanticCache` **module itself**, not a
404
- > per-memory instance. Its cache store, vector store, and configuration are
405
- > process-global — two memories with `enable_cache: true` share one cache. It
406
- > also embeds every query, so `fetch` costs an embedding call.
407
-
408
- ## Best Practices
409
-
410
- ### 1. Use Descriptive Keys
411
-
412
- ```ruby
413
- # Good
414
- robot.memory[:classification_intent] = "billing"
415
- robot.memory[:user_last_order_id] = "ord_456"
416
-
417
- # Bad
418
- robot.memory[:x] = "billing"
419
- robot.memory[:temp1] = "ord_456"
420
- ```
421
-
422
- ### 2. Use Data Hash for Structured Runtime Input
423
-
424
- ```ruby
425
- memory = RobotLab.create_memory(
426
- data: { order_id: "123", priority: "high", customer_tier: "gold" }
427
- )
428
-
429
- # Access via data proxy
430
- memory.data.order_id # => "123"
431
- memory.data.priority # => "high"
432
- memory.data.customer_tier # => "gold"
433
- ```
434
-
435
- ### 3. Clean Up Temporary Values
436
-
437
- ```ruby
438
- # After processing is done
439
- robot.memory.delete(:temp_calculation)
440
- robot.memory.delete(:intermediate_result)
441
- ```
442
-
443
- ### 4. Document Memory Keys
444
-
445
- ```ruby
446
- # In your robot definitions, document expected keys:
447
- #
448
- # Memory keys used by this pipeline:
449
- # - :intent - Classification result (set by classifier)
450
- # - :entities - Extracted entities (set by entity_extractor)
451
- # - :response - Final response draft (set by responder)
452
- ```
453
-
454
- ## Next Steps
455
-
456
- - [Building Robots](building-robots.md) - Using memory in robots
457
- - [Creating Networks](creating-networks.md) - Shared memory in networks
458
- - [API Reference: Memory](../api/core/memory.md) - Complete API