robot_lab 0.2.8 → 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 (262) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +65 -0
  3. data/README.md +16 -18
  4. data/lib/robot_lab/ask_user.rb +3 -3
  5. data/lib/robot_lab/config/defaults.yml +5 -5
  6. data/lib/robot_lab/config.rb +2 -2
  7. data/lib/robot_lab/mcp/transports/streamable_http.rb +2 -4
  8. data/lib/robot_lab/memory.rb +16 -7
  9. data/lib/robot_lab/robot/hooking.rb +26 -0
  10. data/lib/robot_lab/robot/result_building.rb +119 -0
  11. data/lib/robot_lab/robot.rb +37 -120
  12. data/lib/robot_lab/run_config.rb +52 -20
  13. data/lib/robot_lab/tool.rb +7 -12
  14. data/lib/robot_lab/version.rb +1 -1
  15. data/lib/robot_lab.rb +3 -3
  16. metadata +15 -274
  17. data/.architecture/AGENTS.md +0 -32
  18. data/.architecture/config.yml +0 -8
  19. data/.architecture/members.yml +0 -60
  20. data/.architecture/reviews/feature-free-will.md +0 -490
  21. data/.architecture/reviews/overall-codebase.md +0 -427
  22. data/.claude/settings.local.json +0 -57
  23. data/.codex/config.toml +0 -2
  24. data/.envrc +0 -6
  25. data/.github/workflows/deploy-github-pages.yml +0 -52
  26. data/.irbrc +0 -6
  27. data/.loki +0 -65
  28. data/.quality/reek_baseline.txt +0 -43
  29. data/.rubocop.yml +0 -10
  30. data/Archspec.rb +0 -44
  31. data/CLAUDE.md +0 -139
  32. data/COMMITS.md +0 -196
  33. data/Rakefile +0 -193
  34. data/_typos.toml +0 -21
  35. data/agent2agent_review.md +0 -192
  36. data/agentf_improvements.md +0 -253
  37. data/agents.md +0 -14
  38. data/docs/api/core/index.md +0 -147
  39. data/docs/api/core/memory.md +0 -578
  40. data/docs/api/core/network.md +0 -553
  41. data/docs/api/core/result.md +0 -211
  42. data/docs/api/core/robot.md +0 -1667
  43. data/docs/api/core/state.md +0 -145
  44. data/docs/api/core/tool.md +0 -520
  45. data/docs/api/errors.md +0 -323
  46. data/docs/api/hooks.md +0 -469
  47. data/docs/api/index.md +0 -137
  48. data/docs/api/mcp/client.md +0 -387
  49. data/docs/api/mcp/index.md +0 -267
  50. data/docs/api/mcp/server.md +0 -252
  51. data/docs/api/mcp/transports.md +0 -339
  52. data/docs/api/messages/index.md +0 -126
  53. data/docs/api/messages/text-message.md +0 -170
  54. data/docs/api/messages/tool-call-message.md +0 -206
  55. data/docs/api/messages/tool-result-message.md +0 -242
  56. data/docs/api/messages/user-message.md +0 -232
  57. data/docs/api/skills.md +0 -423
  58. data/docs/api/streaming/context.md +0 -280
  59. data/docs/api/streaming/events.md +0 -185
  60. data/docs/api/streaming/index.md +0 -156
  61. data/docs/api/support.md +0 -846
  62. data/docs/architecture/core-concepts.md +0 -575
  63. data/docs/architecture/index.md +0 -252
  64. data/docs/architecture/message-flow.md +0 -366
  65. data/docs/architecture/network-orchestration.md +0 -478
  66. data/docs/architecture/robot-execution.md +0 -396
  67. data/docs/architecture/state-management.md +0 -410
  68. data/docs/assets/css/custom.css +0 -56
  69. data/docs/assets/images/robot_lab.jpg +0 -0
  70. data/docs/concepts.md +0 -641
  71. data/docs/examples/basic-chat.md +0 -258
  72. data/docs/examples/index.md +0 -425
  73. data/docs/examples/mcp-server.md +0 -403
  74. data/docs/examples/multi-robot-network.md +0 -476
  75. data/docs/examples/tool-usage.md +0 -468
  76. data/docs/getting-started/configuration.md +0 -645
  77. data/docs/getting-started/index.md +0 -56
  78. data/docs/getting-started/installation.md +0 -200
  79. data/docs/getting-started/quick-start.md +0 -293
  80. data/docs/guides/building-robots.md +0 -1187
  81. data/docs/guides/creating-networks.md +0 -736
  82. data/docs/guides/hooks.md +0 -1206
  83. data/docs/guides/index.md +0 -78
  84. data/docs/guides/knowledge.md +0 -219
  85. data/docs/guides/mcp-integration.md +0 -740
  86. data/docs/guides/memory.md +0 -458
  87. data/docs/guides/observability.md +0 -733
  88. data/docs/guides/streaming.md +0 -323
  89. data/docs/guides/using-tools.md +0 -780
  90. data/docs/index.md +0 -280
  91. data/docs/superpowers/plans/2026-05-06-agentskills.md +0 -1303
  92. data/docs/superpowers/specs/2026-05-06-agentskills-design.md +0 -247
  93. data/examples/.envrc +0 -1
  94. data/examples/01_simple_robot.rb +0 -31
  95. data/examples/02_tools.rb +0 -109
  96. data/examples/03_network.rb +0 -118
  97. data/examples/04_mcp.rb +0 -215
  98. data/examples/05_streaming.rb +0 -137
  99. data/examples/06_prompt_templates.rb +0 -325
  100. data/examples/07_network_memory.rb +0 -313
  101. data/examples/08_llm_config.rb +0 -162
  102. data/examples/09_chaining.rb +0 -251
  103. data/examples/10_memory.rb +0 -315
  104. data/examples/11_network_introspection.rb +0 -239
  105. data/examples/12_message_bus.rb +0 -67
  106. data/examples/13_spawn.rb +0 -85
  107. data/examples/14_rusty_circuit/.envrc +0 -1
  108. data/examples/14_rusty_circuit/.gitignore +0 -1
  109. data/examples/14_rusty_circuit/README.md +0 -1
  110. data/examples/14_rusty_circuit/comic.rb +0 -149
  111. data/examples/14_rusty_circuit/display.rb +0 -217
  112. data/examples/14_rusty_circuit/heckler.rb +0 -65
  113. data/examples/14_rusty_circuit/open_mic.rb +0 -132
  114. data/examples/14_rusty_circuit/prompts/open_mic_comic.md +0 -20
  115. data/examples/14_rusty_circuit/prompts/open_mic_heckler.md +0 -23
  116. data/examples/14_rusty_circuit/prompts/open_mic_scout.md +0 -20
  117. data/examples/14_rusty_circuit/scout.rb +0 -165
  118. data/examples/14_rusty_circuit/show.log +0 -234
  119. data/examples/15_memory_network_and_bus/.envrc +0 -1
  120. data/examples/15_memory_network_and_bus/editor_in_chief.rb +0 -24
  121. data/examples/15_memory_network_and_bus/editorial_pipeline.rb +0 -213
  122. data/examples/15_memory_network_and_bus/linux_writer.rb +0 -80
  123. data/examples/15_memory_network_and_bus/os_editor.rb +0 -48
  124. data/examples/15_memory_network_and_bus/os_writer.rb +0 -49
  125. data/examples/15_memory_network_and_bus/output/combined_article.md +0 -13
  126. data/examples/15_memory_network_and_bus/output/final_article.md +0 -13
  127. data/examples/15_memory_network_and_bus/output/linux_draft.md +0 -7
  128. data/examples/15_memory_network_and_bus/output/mac_draft.md +0 -7
  129. data/examples/15_memory_network_and_bus/output/memory.json +0 -13
  130. data/examples/15_memory_network_and_bus/output/revision_1.md +0 -18
  131. data/examples/15_memory_network_and_bus/output/revision_2.md +0 -13
  132. data/examples/15_memory_network_and_bus/output/windows_draft.md +0 -7
  133. data/examples/15_memory_network_and_bus/prompts/os_advocate.md +0 -13
  134. data/examples/15_memory_network_and_bus/prompts/os_chief.md +0 -13
  135. data/examples/15_memory_network_and_bus/prompts/os_editor.md +0 -13
  136. data/examples/16_writers_room/.envrc +0 -1
  137. data/examples/16_writers_room/display.rb +0 -158
  138. data/examples/16_writers_room/output/.gitignore +0 -4
  139. data/examples/16_writers_room/output/README.md +0 -69
  140. data/examples/16_writers_room/output/opus_001.md +0 -263
  141. data/examples/16_writers_room/output/opus_001_notes.log +0 -470
  142. data/examples/16_writers_room/output/opus_002.md +0 -245
  143. data/examples/16_writers_room/output/opus_002_notes.log +0 -546
  144. data/examples/16_writers_room/output/opus_002_screenplay.md +0 -7989
  145. data/examples/16_writers_room/output/opus_002_screenplay_notes.md +0 -993
  146. data/examples/16_writers_room/prompts/screenplay_writer.md +0 -66
  147. data/examples/16_writers_room/prompts/writer.md +0 -37
  148. data/examples/16_writers_room/room.rb +0 -186
  149. data/examples/16_writers_room/tools.rb +0 -173
  150. data/examples/16_writers_room/writer.rb +0 -121
  151. data/examples/16_writers_room/writers_room.rb +0 -256
  152. data/examples/17_skills.rb +0 -234
  153. data/examples/18_rails/.envrc +0 -3
  154. data/examples/18_rails/.gitignore +0 -5
  155. data/examples/18_rails/Gemfile +0 -11
  156. data/examples/18_rails/README.md +0 -67
  157. data/examples/18_rails/Rakefile +0 -4
  158. data/examples/18_rails/app/controllers/application_controller.rb +0 -4
  159. data/examples/18_rails/app/controllers/chat_controller.rb +0 -50
  160. data/examples/18_rails/app/jobs/application_job.rb +0 -4
  161. data/examples/18_rails/app/jobs/robot_run_job.rb +0 -25
  162. data/examples/18_rails/app/models/application_record.rb +0 -5
  163. data/examples/18_rails/app/models/robot_lab_result.rb +0 -36
  164. data/examples/18_rails/app/models/robot_lab_thread.rb +0 -23
  165. data/examples/18_rails/app/robots/chat_robot.rb +0 -25
  166. data/examples/18_rails/app/tools/time_tool.rb +0 -9
  167. data/examples/18_rails/app/views/chat/_user_message.html.erb +0 -1
  168. data/examples/18_rails/app/views/chat/index.html.erb +0 -67
  169. data/examples/18_rails/app/views/layouts/application.html.erb +0 -49
  170. data/examples/18_rails/bin/dev +0 -7
  171. data/examples/18_rails/bin/rails +0 -6
  172. data/examples/18_rails/bin/setup +0 -15
  173. data/examples/18_rails/config/application.rb +0 -33
  174. data/examples/18_rails/config/cable.yml +0 -2
  175. data/examples/18_rails/config/database.yml +0 -5
  176. data/examples/18_rails/config/environment.rb +0 -4
  177. data/examples/18_rails/config/initializers/robot_lab.rb +0 -11
  178. data/examples/18_rails/config/routes.rb +0 -6
  179. data/examples/18_rails/config.ru +0 -4
  180. data/examples/18_rails/db/migrate/001_create_robot_lab_tables.rb +0 -32
  181. data/examples/19_token_tracking.rb +0 -138
  182. data/examples/20_circuit_breaker.rb +0 -147
  183. data/examples/21_learning_loop.rb +0 -181
  184. data/examples/22_context_compression.rb +0 -172
  185. data/examples/23_convergence.rb +0 -133
  186. data/examples/24_structured_delegation.rb +0 -151
  187. data/examples/25_history_search/conversation.jsonl +0 -30
  188. data/examples/25_history_search.rb +0 -133
  189. data/examples/26_document_store/api_versioning_adr.md +0 -52
  190. data/examples/26_document_store/incident_postmortem.md +0 -46
  191. data/examples/26_document_store/postgres_runbook.md +0 -49
  192. data/examples/26_document_store/redis_caching_guide.md +0 -48
  193. data/examples/26_document_store/sidekiq_guide.md +0 -51
  194. data/examples/26_document_store.rb +0 -140
  195. data/examples/27_incident_response/incident_response.rb +0 -261
  196. data/examples/28_mcp_discovery.rb +0 -113
  197. data/examples/29_ractor_tools.rb +0 -240
  198. data/examples/30_ractor_network.rb +0 -252
  199. data/examples/31_launch_assessment.rb +0 -246
  200. data/examples/32_newsletter_reader.rb +0 -193
  201. data/examples/33_stock_generator.rb +0 -80
  202. data/examples/33_stock_predictor.rb +0 -327
  203. data/examples/34_agentskills.rb +0 -76
  204. data/examples/35_hooks.rb +0 -266
  205. data/examples/README.md +0 -533
  206. data/examples/common.rb +0 -148
  207. data/examples/prompts/assistant.md +0 -5
  208. data/examples/prompts/audit_trail.md +0 -8
  209. data/examples/prompts/billing.md +0 -10
  210. data/examples/prompts/classifier.md +0 -7
  211. data/examples/prompts/comedian.md +0 -6
  212. data/examples/prompts/comedy_critic.md +0 -10
  213. data/examples/prompts/configurable.md +0 -9
  214. data/examples/prompts/dispatcher.md +0 -12
  215. data/examples/prompts/entity_extractor.md +0 -14
  216. data/examples/prompts/escalation.md +0 -42
  217. data/examples/prompts/frontmatter_mcp_test.md +0 -9
  218. data/examples/prompts/frontmatter_named_test.md +0 -5
  219. data/examples/prompts/frontmatter_tools_test.md +0 -6
  220. data/examples/prompts/general.md +0 -7
  221. data/examples/prompts/github_assistant.md +0 -14
  222. data/examples/prompts/helper.md +0 -4
  223. data/examples/prompts/incident_responder.md +0 -18
  224. data/examples/prompts/keyword_extractor.md +0 -11
  225. data/examples/prompts/llm_config_demo.md +0 -20
  226. data/examples/prompts/order_support.md +0 -35
  227. data/examples/prompts/os_advocate.md +0 -13
  228. data/examples/prompts/os_chief.md +0 -13
  229. data/examples/prompts/os_editor.md +0 -13
  230. data/examples/prompts/parameterized_main_test.md +0 -6
  231. data/examples/prompts/pii_redactor.md +0 -7
  232. data/examples/prompts/product_support.md +0 -37
  233. data/examples/prompts/runbook_protocol.md +0 -12
  234. data/examples/prompts/sentiment_analyzer.md +0 -12
  235. data/examples/prompts/skill_a_test.md +0 -4
  236. data/examples/prompts/skill_b_test.md +0 -4
  237. data/examples/prompts/skill_config_test.md +0 -5
  238. data/examples/prompts/skill_cycle_a_test.md +0 -6
  239. data/examples/prompts/skill_cycle_b_test.md +0 -6
  240. data/examples/prompts/skill_description_test.md +0 -4
  241. data/examples/prompts/skill_leaf_test.md +0 -4
  242. data/examples/prompts/skill_nested_test.md +0 -6
  243. data/examples/prompts/skill_refs_main_test.md +0 -6
  244. data/examples/prompts/skill_self_ref_test.md +0 -6
  245. data/examples/prompts/skill_with_mcp_test.md +0 -9
  246. data/examples/prompts/skill_with_params_test.md +0 -6
  247. data/examples/prompts/skill_with_robot_name_test.md +0 -5
  248. data/examples/prompts/skill_with_tools_test.md +0 -6
  249. data/examples/prompts/sre_compliance.md +0 -10
  250. data/examples/prompts/structured_output.md +0 -7
  251. data/examples/prompts/synthesizer.md +0 -17
  252. data/examples/prompts/technical.md +0 -10
  253. data/examples/prompts/template_with_skills_test.md +0 -6
  254. data/examples/prompts/triage.md +0 -22
  255. data/examples/ruboruby.md +0 -423
  256. data/examples/xyzzy.rb +0 -97
  257. data/logfile +0 -8
  258. data/mkdocs.yml +0 -209
  259. data/robot_concurrency.md +0 -38
  260. data/simple_acp_review.md +0 -298
  261. data/temp.md +0 -6
  262. 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`
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` |
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