robot_lab 0.3.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (274) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +60 -0
  3. data/README.md +29 -19
  4. data/lib/robot_lab/errors.rb +4 -0
  5. data/lib/robot_lab/hook_context.rb +9 -2
  6. data/lib/robot_lab/mcp/registry.rb +99 -0
  7. data/lib/robot_lab/mcp/server.rb +47 -83
  8. data/lib/robot_lab/mcp/server_discovery.rb +22 -9
  9. data/lib/robot_lab/mcp.rb +259 -0
  10. data/lib/robot_lab/robot/mcp_management.rb +159 -105
  11. data/lib/robot_lab/robot/tool_call_gating.rb +144 -0
  12. data/lib/robot_lab/robot.rb +72 -99
  13. data/lib/robot_lab/run_config.rb +3 -2
  14. data/lib/robot_lab/tool.rb +8 -23
  15. data/lib/robot_lab/version.rb +1 -1
  16. data/lib/robot_lab.rb +1 -4
  17. metadata +16 -292
  18. data/.architecture/AGENTS.md +0 -32
  19. data/.architecture/config.yml +0 -8
  20. data/.architecture/members.yml +0 -60
  21. data/.architecture/reviews/feature-free-will.md +0 -490
  22. data/.architecture/reviews/overall-codebase.md +0 -427
  23. data/.claude/settings.local.json +0 -57
  24. data/.codex/config.toml +0 -2
  25. data/.envrc +0 -6
  26. data/.github/workflows/deploy-github-pages.yml +0 -52
  27. data/.irbrc +0 -6
  28. data/.loki +0 -65
  29. data/.quality/reek_baseline.txt +0 -43
  30. data/.rubocop.yml +0 -10
  31. data/Archspec.rb +0 -44
  32. data/CLAUDE.md +0 -141
  33. data/COMMITS.md +0 -196
  34. data/Rakefile +0 -193
  35. data/_typos.toml +0 -21
  36. data/agent2agent_review.md +0 -192
  37. data/agentf_improvements.md +0 -253
  38. data/agents.md +0 -14
  39. data/docs/api/core/index.md +0 -147
  40. data/docs/api/core/memory.md +0 -578
  41. data/docs/api/core/network.md +0 -553
  42. data/docs/api/core/result.md +0 -211
  43. data/docs/api/core/robot.md +0 -1667
  44. data/docs/api/core/state.md +0 -145
  45. data/docs/api/core/tool.md +0 -520
  46. data/docs/api/errors.md +0 -323
  47. data/docs/api/hooks.md +0 -469
  48. data/docs/api/index.md +0 -137
  49. data/docs/api/mcp/client.md +0 -387
  50. data/docs/api/mcp/index.md +0 -267
  51. data/docs/api/mcp/server.md +0 -252
  52. data/docs/api/mcp/transports.md +0 -339
  53. data/docs/api/messages/index.md +0 -126
  54. data/docs/api/messages/text-message.md +0 -170
  55. data/docs/api/messages/tool-call-message.md +0 -206
  56. data/docs/api/messages/tool-result-message.md +0 -242
  57. data/docs/api/messages/user-message.md +0 -232
  58. data/docs/api/skills.md +0 -423
  59. data/docs/api/streaming/context.md +0 -280
  60. data/docs/api/streaming/events.md +0 -185
  61. data/docs/api/streaming/index.md +0 -156
  62. data/docs/api/support.md +0 -846
  63. data/docs/architecture/core-concepts.md +0 -575
  64. data/docs/architecture/index.md +0 -252
  65. data/docs/architecture/message-flow.md +0 -366
  66. data/docs/architecture/network-orchestration.md +0 -478
  67. data/docs/architecture/robot-execution.md +0 -396
  68. data/docs/architecture/state-management.md +0 -410
  69. data/docs/assets/css/custom.css +0 -56
  70. data/docs/assets/images/robot_lab.jpg +0 -0
  71. data/docs/concepts.md +0 -641
  72. data/docs/examples/basic-chat.md +0 -258
  73. data/docs/examples/index.md +0 -425
  74. data/docs/examples/mcp-server.md +0 -403
  75. data/docs/examples/multi-robot-network.md +0 -476
  76. data/docs/examples/tool-usage.md +0 -468
  77. data/docs/getting-started/configuration.md +0 -645
  78. data/docs/getting-started/index.md +0 -56
  79. data/docs/getting-started/installation.md +0 -197
  80. data/docs/getting-started/quick-start.md +0 -293
  81. data/docs/guides/building-robots.md +0 -1187
  82. data/docs/guides/creating-networks.md +0 -736
  83. data/docs/guides/hooks.md +0 -1206
  84. data/docs/guides/index.md +0 -78
  85. data/docs/guides/knowledge.md +0 -219
  86. data/docs/guides/mcp-integration.md +0 -740
  87. data/docs/guides/memory.md +0 -458
  88. data/docs/guides/observability.md +0 -733
  89. data/docs/guides/streaming.md +0 -323
  90. data/docs/guides/using-tools.md +0 -780
  91. data/docs/index.md +0 -280
  92. data/docs/superpowers/plans/2026-05-06-agentskills.md +0 -1303
  93. data/docs/superpowers/specs/2026-05-06-agentskills-design.md +0 -247
  94. data/examples/.envrc +0 -3
  95. data/examples/01_simple_robot.rb +0 -31
  96. data/examples/02_tools.rb +0 -109
  97. data/examples/03_network.rb +0 -118
  98. data/examples/04_mcp.rb +0 -217
  99. data/examples/05_streaming.rb +0 -137
  100. data/examples/06_prompt_templates.rb +0 -325
  101. data/examples/07_network_memory.rb +0 -313
  102. data/examples/08_llm_config.rb +0 -162
  103. data/examples/09_chaining.rb +0 -251
  104. data/examples/10_memory.rb +0 -315
  105. data/examples/11_network_introspection.rb +0 -239
  106. data/examples/12_message_bus.rb +0 -67
  107. data/examples/13_spawn.rb +0 -85
  108. data/examples/14_rusty_circuit/.envrc +0 -1
  109. data/examples/14_rusty_circuit/.gitignore +0 -1
  110. data/examples/14_rusty_circuit/README.md +0 -1
  111. data/examples/14_rusty_circuit/comic.rb +0 -149
  112. data/examples/14_rusty_circuit/display.rb +0 -217
  113. data/examples/14_rusty_circuit/heckler.rb +0 -65
  114. data/examples/14_rusty_circuit/open_mic.rb +0 -132
  115. data/examples/14_rusty_circuit/prompts/open_mic_comic.md +0 -20
  116. data/examples/14_rusty_circuit/prompts/open_mic_heckler.md +0 -23
  117. data/examples/14_rusty_circuit/prompts/open_mic_scout.md +0 -20
  118. data/examples/14_rusty_circuit/scout.rb +0 -165
  119. data/examples/14_rusty_circuit/show.log +0 -234
  120. data/examples/15_memory_network_and_bus/.envrc +0 -1
  121. data/examples/15_memory_network_and_bus/README.md +0 -66
  122. data/examples/15_memory_network_and_bus/editor_in_chief.rb +0 -24
  123. data/examples/15_memory_network_and_bus/editorial_pipeline.rb +0 -213
  124. data/examples/15_memory_network_and_bus/linux_writer.rb +0 -80
  125. data/examples/15_memory_network_and_bus/os_editor.rb +0 -48
  126. data/examples/15_memory_network_and_bus/os_writer.rb +0 -49
  127. data/examples/15_memory_network_and_bus/output/combined_article.md +0 -11
  128. data/examples/15_memory_network_and_bus/output/final_article.md +0 -8
  129. data/examples/15_memory_network_and_bus/output/linux_draft.md +0 -7
  130. data/examples/15_memory_network_and_bus/output/mac_draft.md +0 -7
  131. data/examples/15_memory_network_and_bus/output/memory.json +0 -13
  132. data/examples/15_memory_network_and_bus/output/revision_1.md +0 -29
  133. data/examples/15_memory_network_and_bus/output/revision_2.md +0 -50
  134. data/examples/15_memory_network_and_bus/output/revision_3.md +0 -8
  135. data/examples/15_memory_network_and_bus/output/windows_draft.md +0 -7
  136. data/examples/15_memory_network_and_bus/prompts/os_advocate.md +0 -13
  137. data/examples/15_memory_network_and_bus/prompts/os_chief.md +0 -13
  138. data/examples/15_memory_network_and_bus/prompts/os_editor.md +0 -13
  139. data/examples/16_writers_room/.envrc +0 -1
  140. data/examples/16_writers_room/display.rb +0 -158
  141. data/examples/16_writers_room/output/.gitignore +0 -4
  142. data/examples/16_writers_room/output/README.md +0 -69
  143. data/examples/16_writers_room/output/opus_001.md +0 -263
  144. data/examples/16_writers_room/output/opus_001_notes.log +0 -470
  145. data/examples/16_writers_room/output/opus_002.md +0 -245
  146. data/examples/16_writers_room/output/opus_002_notes.log +0 -546
  147. data/examples/16_writers_room/output/opus_002_screenplay.md +0 -7989
  148. data/examples/16_writers_room/output/opus_002_screenplay_notes.md +0 -993
  149. data/examples/16_writers_room/prompts/screenplay_writer.md +0 -66
  150. data/examples/16_writers_room/prompts/writer.md +0 -37
  151. data/examples/16_writers_room/room.rb +0 -186
  152. data/examples/16_writers_room/tools.rb +0 -173
  153. data/examples/16_writers_room/writer.rb +0 -121
  154. data/examples/16_writers_room/writers_room.rb +0 -256
  155. data/examples/17_skills.rb +0 -234
  156. data/examples/18_rails/.envrc +0 -3
  157. data/examples/18_rails/.gitignore +0 -5
  158. data/examples/18_rails/Gemfile +0 -11
  159. data/examples/18_rails/README.md +0 -67
  160. data/examples/18_rails/Rakefile +0 -4
  161. data/examples/18_rails/app/controllers/application_controller.rb +0 -4
  162. data/examples/18_rails/app/controllers/chat_controller.rb +0 -50
  163. data/examples/18_rails/app/jobs/application_job.rb +0 -4
  164. data/examples/18_rails/app/jobs/robot_run_job.rb +0 -25
  165. data/examples/18_rails/app/models/application_record.rb +0 -5
  166. data/examples/18_rails/app/models/robot_lab_result.rb +0 -36
  167. data/examples/18_rails/app/models/robot_lab_thread.rb +0 -23
  168. data/examples/18_rails/app/robots/chat_robot.rb +0 -25
  169. data/examples/18_rails/app/tools/time_tool.rb +0 -9
  170. data/examples/18_rails/app/views/chat/_user_message.html.erb +0 -1
  171. data/examples/18_rails/app/views/chat/index.html.erb +0 -67
  172. data/examples/18_rails/app/views/layouts/application.html.erb +0 -49
  173. data/examples/18_rails/bin/dev +0 -7
  174. data/examples/18_rails/bin/rails +0 -6
  175. data/examples/18_rails/bin/setup +0 -15
  176. data/examples/18_rails/config/application.rb +0 -33
  177. data/examples/18_rails/config/cable.yml +0 -2
  178. data/examples/18_rails/config/database.yml +0 -5
  179. data/examples/18_rails/config/environment.rb +0 -4
  180. data/examples/18_rails/config/initializers/robot_lab.rb +0 -11
  181. data/examples/18_rails/config/routes.rb +0 -6
  182. data/examples/18_rails/config.ru +0 -4
  183. data/examples/18_rails/db/migrate/001_create_robot_lab_tables.rb +0 -32
  184. data/examples/19_token_tracking.rb +0 -138
  185. data/examples/20_circuit_breaker.rb +0 -147
  186. data/examples/21_learning_loop.rb +0 -181
  187. data/examples/22_context_compression.rb +0 -172
  188. data/examples/23_convergence.rb +0 -133
  189. data/examples/24_structured_delegation.rb +0 -151
  190. data/examples/25_history_search/conversation.jsonl +0 -30
  191. data/examples/25_history_search.rb +0 -133
  192. data/examples/26_document_store/api_versioning_adr.md +0 -52
  193. data/examples/26_document_store/incident_postmortem.md +0 -46
  194. data/examples/26_document_store/postgres_runbook.md +0 -49
  195. data/examples/26_document_store/redis_caching_guide.md +0 -48
  196. data/examples/26_document_store/sidekiq_guide.md +0 -51
  197. data/examples/26_document_store.rb +0 -140
  198. data/examples/27_incident_response/README.md +0 -65
  199. data/examples/27_incident_response/incident_response.rb +0 -261
  200. data/examples/28_mcp_discovery.rb +0 -113
  201. data/examples/29_ractor_tools.rb +0 -240
  202. data/examples/30_ractor_network.rb +0 -252
  203. data/examples/31_launch_assessment.rb +0 -246
  204. data/examples/32_newsletter_reader.rb +0 -193
  205. data/examples/33_stock_generator.rb +0 -80
  206. data/examples/33_stock_predictor.rb +0 -327
  207. data/examples/34_agentskills.rb +0 -76
  208. data/examples/35_hooks.rb +0 -266
  209. data/examples/README.md +0 -550
  210. data/examples/common.rb +0 -180
  211. data/examples/prompts/assistant.md +0 -5
  212. data/examples/prompts/audit_trail.md +0 -8
  213. data/examples/prompts/billing.md +0 -10
  214. data/examples/prompts/classifier.md +0 -7
  215. data/examples/prompts/comedian.md +0 -6
  216. data/examples/prompts/comedy_critic.md +0 -10
  217. data/examples/prompts/configurable.md +0 -9
  218. data/examples/prompts/dispatcher.md +0 -12
  219. data/examples/prompts/entity_extractor.md +0 -14
  220. data/examples/prompts/escalation.md +0 -42
  221. data/examples/prompts/frontmatter_mcp_test.md +0 -9
  222. data/examples/prompts/frontmatter_named_test.md +0 -5
  223. data/examples/prompts/frontmatter_tools_test.md +0 -6
  224. data/examples/prompts/general.md +0 -7
  225. data/examples/prompts/github_assistant.md +0 -14
  226. data/examples/prompts/helper.md +0 -4
  227. data/examples/prompts/incident_responder.md +0 -18
  228. data/examples/prompts/keyword_extractor.md +0 -11
  229. data/examples/prompts/llm_config_demo.md +0 -20
  230. data/examples/prompts/order_support.md +0 -35
  231. data/examples/prompts/os_advocate.md +0 -13
  232. data/examples/prompts/os_chief.md +0 -13
  233. data/examples/prompts/os_editor.md +0 -13
  234. data/examples/prompts/parameterized_main_test.md +0 -6
  235. data/examples/prompts/pii_redactor.md +0 -7
  236. data/examples/prompts/product_support.md +0 -37
  237. data/examples/prompts/runbook_protocol.md +0 -12
  238. data/examples/prompts/sentiment_analyzer.md +0 -12
  239. data/examples/prompts/skill_a_test.md +0 -4
  240. data/examples/prompts/skill_b_test.md +0 -4
  241. data/examples/prompts/skill_config_test.md +0 -5
  242. data/examples/prompts/skill_cycle_a_test.md +0 -6
  243. data/examples/prompts/skill_cycle_b_test.md +0 -6
  244. data/examples/prompts/skill_description_test.md +0 -4
  245. data/examples/prompts/skill_leaf_test.md +0 -4
  246. data/examples/prompts/skill_nested_test.md +0 -6
  247. data/examples/prompts/skill_refs_main_test.md +0 -6
  248. data/examples/prompts/skill_self_ref_test.md +0 -6
  249. data/examples/prompts/skill_with_mcp_test.md +0 -9
  250. data/examples/prompts/skill_with_params_test.md +0 -6
  251. data/examples/prompts/skill_with_robot_name_test.md +0 -5
  252. data/examples/prompts/skill_with_tools_test.md +0 -6
  253. data/examples/prompts/sre_compliance.md +0 -10
  254. data/examples/prompts/structured_output.md +0 -7
  255. data/examples/prompts/synthesizer.md +0 -17
  256. data/examples/prompts/technical.md +0 -10
  257. data/examples/prompts/template_with_skills_test.md +0 -6
  258. data/examples/prompts/triage.md +0 -22
  259. data/examples/ruboruby.md +0 -423
  260. data/examples/run_all.rb +0 -60
  261. data/examples/xyzzy.rb +0 -97
  262. data/lib/robot_lab/mcp/client.rb +0 -223
  263. data/lib/robot_lab/mcp/connection_poller.rb +0 -195
  264. data/lib/robot_lab/mcp/transports/base.rb +0 -64
  265. data/lib/robot_lab/mcp/transports/sse.rb +0 -120
  266. data/lib/robot_lab/mcp/transports/stdio.rb +0 -170
  267. data/lib/robot_lab/mcp/transports/streamable_http.rb +0 -141
  268. data/lib/robot_lab/mcp/transports/websocket.rb +0 -111
  269. data/logfile +0 -8
  270. data/mkdocs.yml +0 -209
  271. data/robot_concurrency.md +0 -38
  272. data/simple_acp_review.md +0 -298
  273. data/temp.md +0 -6
  274. data/tool_manifest_plan.md +0 -155
@@ -1,578 +0,0 @@
1
- # Memory
2
-
3
- Reactive key-value store for sharing data between robots.
4
-
5
- ## Class: `RobotLab::Memory`
6
-
7
- ```ruby
8
- memory = robot.memory
9
-
10
- memory.set(:key, "value")
11
- value = memory.get(:key)
12
- ```
13
-
14
- ## Constants
15
-
16
- ### RESERVED_KEYS
17
-
18
- ```ruby
19
- Memory::RESERVED_KEYS # => [:data, :results, :messages, :session_id, :cache]
20
- ```
21
-
22
- Reserved keys with special accessors and behavior.
23
-
24
- ## Constructor
25
-
26
- ```ruby
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 })
54
- ```
55
-
56
- ## Methods
57
-
58
- ### set
59
-
60
- ```ruby
61
- memory.set(:key, value)
62
- ```
63
-
64
- Store a value and notify subscribers asynchronously.
65
-
66
- **Parameters:**
67
-
68
- | Name | Type | Description |
69
- |------|------|-------------|
70
- | `key` | `Symbol`, `String` | Storage key |
71
- | `value` | `Object` | Value to store |
72
-
73
- ### get
74
-
75
- ```ruby
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
80
- ```
81
-
82
- Retrieve one or more values, optionally waiting until they exist.
83
-
84
- **Parameters:**
85
-
86
- | Name | Type | Default | Description |
87
- |------|------|---------|-------------|
88
- | `*keys` | `Symbol`, `String` | — | One or more keys to retrieve; flattened and symbolized |
89
- | `wait` | `Boolean`, `Numeric` | `false` | `false`: return immediately (nil if missing). `true`: block indefinitely. `Numeric`: block up to that many seconds |
90
-
91
- **Returns:** the single value for one key, a `Hash` keyed by symbol for multiple keys.
92
-
93
- **Raises:** `RobotLab::AwaitTimeout` — `"Timeout waiting for :<key> after <N> seconds"`.
94
-
95
- !!! warning "Blocking semantics"
96
- - On expiry `get` **raises `RobotLab::AwaitTimeout`**; it does not return nil.
97
- Rescue it if a missing value is acceptable.
98
- - With multiple keys the timeout is applied **per missing key**, not to the
99
- call as a whole. `get(:a, :b, :c, wait: 60)` can block for up to 180
100
- seconds if all three are missing.
101
- - `wait: true` blocks with no deadline and can hang forever. Prefer a numeric
102
- timeout in production.
103
- - Waiting is implemented with a pipe (`Waiter`, using `IO#wait_readable`), so
104
- a blocked reader does not spin.
105
-
106
- ```ruby
107
- value = begin
108
- memory.get(:sentiment, wait: 30)
109
- rescue RobotLab::AwaitTimeout
110
- nil
111
- end
112
- ```
113
-
114
- ### key?
115
-
116
- ```ruby
117
- memory.key?(:key) # => Boolean
118
- ```
119
-
120
- Check if key exists. Aliases: `has_key?`, `include?`.
121
-
122
- ### delete
123
-
124
- ```ruby
125
- memory.delete(:key) # => deleted value
126
- ```
127
-
128
- Remove a key. Cannot delete reserved keys.
129
-
130
- ### keys
131
-
132
- ```ruby
133
- memory.keys # => Array<Symbol>
134
- ```
135
-
136
- All keys **excluding** the reserved ones.
137
-
138
- ### all_keys
139
-
140
- ```ruby
141
- memory.all_keys # => Array<Symbol>
142
- ```
143
-
144
- All keys **including** the reserved ones (`:data`, `:results`, `:messages`,
145
- `:session_id`, `:cache`).
146
-
147
- ### clear
148
-
149
- ```ruby
150
- memory.clear
151
- ```
152
-
153
- Clear all non-reserved keys.
154
-
155
- ### reset
156
-
157
- ```ruby
158
- memory.reset
159
- ```
160
-
161
- Reset memory to its initial state: clears the backend, restores `:data` to `{}`,
162
- `:results` and `:messages` to `[]`, `:session_id` to `nil`, and re-installs the
163
- existing cache object. The `StateProxy` returned by `data` is discarded and
164
- rebuilt on next access.
165
-
166
- This resets the **key-value store only**. It has nothing to do with a robot's
167
- chat history — use `robot.clear_messages(keep_system: true)` for that. The two
168
- are independent.
169
-
170
- ### subscribe
171
-
172
- ```ruby
173
- sub_id = memory.subscribe(:key1, :key2) do |change|
174
- puts "#{change.key} changed: #{change.value}"
175
- end
176
- ```
177
-
178
- Subscribe to changes on one or more keys. Callback receives a `MemoryChange` object.
179
- Returns a subscription ID for [`unsubscribe`](#unsubscribe).
180
-
181
- **Raises:** `ArgumentError` if no block is given.
182
-
183
- Only [`set`](#set) notifies subscribers. Writing a reserved key
184
- (`memory[:data] = ...`, `session_id=`, `append_result`) bypasses notification
185
- entirely, as does mutating `memory.data`.
186
-
187
- Callbacks are dispatched through `Async { }`. **Outside a running reactor the
188
- callback runs synchronously on the writer's thread**, so a slow subscriber blocks
189
- the `set` that triggered it.
190
-
191
- **`RobotLab::MemoryChange` attributes:**
192
-
193
- | Attribute | Type | Description |
194
- |-----------|------|-------------|
195
- | `key` | `Symbol` | The changed key |
196
- | `value` | `Object` | New value |
197
- | `previous` | `Object, nil` | Previous value |
198
- | `writer` | `String, nil` | `memory.current_writer` at the time of the write — set to the robot's name for the duration of each `run` |
199
- | `network_name` | `String, nil` | Network name |
200
- | `timestamp` | `Time` | When the change occurred |
201
- | `correlation_id` | `String, nil` | Optional tracing ID |
202
-
203
- **Predicates** — note that each tests *both* sides, so all three are false when
204
- `previous` and `value` are both nil, and all three are false for a nil→nil write:
205
-
206
- | Predicate | Exact definition |
207
- |-----------|------------------|
208
- | `created?` | `previous.nil? && !value.nil?` |
209
- | `updated?` | `!previous.nil? && !value.nil?` |
210
- | `deleted?` | `value.nil? && !previous.nil?` |
211
-
212
- Also available: `to_h` (`.compact`ed, `timestamp` rendered as ISO-8601),
213
- `to_json`, and `MemoryChange.from_hash`.
214
-
215
- ### subscribe_pattern
216
-
217
- ```ruby
218
- sub_id = memory.subscribe_pattern("analysis:*") do |change|
219
- puts "Analysis key #{change.key} updated"
220
- end
221
- ```
222
-
223
- Subscribe to keys matching a glob pattern (`*` and `?` supported). Returns a
224
- subscription ID. **Raises:** `ArgumentError` if no block is given.
225
-
226
- ### unsubscribe
227
-
228
- ```ruby
229
- memory.unsubscribe(sub_id) # => Boolean
230
- ```
231
-
232
- Remove a subscription by its ID (works for both `subscribe` and
233
- `subscribe_pattern`). Returns `true` when something was removed.
234
-
235
- ### unsubscribe_keys
236
-
237
- ```ruby
238
- memory.unsubscribe_keys(:status, :progress) # => self
239
- ```
240
-
241
- Drop **all** key subscriptions for the named keys at once, without needing their
242
- IDs. Pattern subscriptions are unaffected.
243
-
244
- ### subscribed?
245
-
246
- ```ruby
247
- memory.subscribed?(:status) # => Boolean
248
- ```
249
-
250
- Whether any subscriber — key-based or pattern-based — would be notified for `key`.
251
-
252
- ### merge!
253
-
254
- ```ruby
255
- memory.merge!(key1: "value1", key2: "value2") # => self
256
- ```
257
-
258
- Merge multiple key-value pairs into memory. Each pair is assigned through `[]=`,
259
- so non-reserved keys go through the reactive `set` path and do notify subscribers.
260
-
261
- ## Results and History
262
-
263
- ### append_result
264
-
265
- ```ruby
266
- memory.append_result(robot_result) # => self
267
- ```
268
-
269
- Push a `RobotResult` onto the accumulated `:results` array. Bypasses subscriber
270
- notification.
271
-
272
- ### set_results
273
-
274
- ```ruby
275
- memory.set_results(array_of_results) # => self
276
- ```
277
-
278
- Replace the whole `:results` array (used when loading from persistence).
279
-
280
- ### results_from
281
-
282
- ```ruby
283
- memory.results_from(5) # => Array<RobotResult>
284
- ```
285
-
286
- Results from the given index onward — for incremental saves. Returns `[]` when
287
- the index is past the end.
288
-
289
- ### format_history
290
-
291
- ```ruby
292
- memory.format_history(formatter: nil) # => Array<Message>
293
- ```
294
-
295
- `messages` followed by every result flat-mapped through `formatter`. Pass a
296
- `Proc` for `formatter:` to control how a `RobotResult` becomes messages; the
297
- default formatter is used when omitted.
298
-
299
- ## Backend
300
-
301
- ### redis?
302
-
303
- ```ruby
304
- memory.redis? # => Boolean
305
- ```
306
-
307
- Whether this memory is backed by Redis rather than the in-process Hash. The
308
- `backend: :auto` default tries Redis and falls back to a Hash; `backend: :hash`
309
- forces the Hash.
310
-
311
- ### network_name
312
-
313
- ```ruby
314
- memory.network_name # => String, nil
315
- ```
316
-
317
- The network this memory belongs to, set once at construction and read-only
318
- thereafter. `Network` creates its shared memory as `Memory.new(network_name: name)`;
319
- a standalone robot's inherent memory has `nil`. It is copied onto every
320
- `MemoryChange` so a subscriber can tell which network a write came from, and it is
321
- preserved by `clone`/`dup` — but **not** by `from_hash`.
322
-
323
- ### current_writer / current_writer=
324
-
325
- ```ruby
326
- memory.current_writer # => String, nil
327
- memory.current_writer = "bot"
328
- ```
329
-
330
- The name attributed to writes, surfaced as `MemoryChange#writer`. `Robot#run`
331
- sets this to the robot's name for the duration of the run and restores the
332
- previous value in an `ensure` block, so nested and concurrent runs attribute
333
- correctly.
334
-
335
- ## Document Store
336
-
337
- These four methods require the **`robot_lab-document_store`** extension gem. Without
338
- it every one of them raises
339
- `RobotLab::DependencyError: document storage requires the robot_lab-document_store gem.`
340
-
341
- | Method | Returns | Description |
342
- |--------|---------|-------------|
343
- | `store_document(key, text)` | `self` | Embed `text` and store it under `key` |
344
- | `search_documents(query, limit: 5)` | `Array<Hash>` | Hits sorted by score descending; each hash has `:key`, `:text`, `:score` |
345
- | `document_keys` | `Array<Symbol>` | Keys of all stored documents |
346
- | `delete_document(key)` | `self` | Remove a document |
347
-
348
- ```ruby
349
- memory.store_document(:readme, File.read("README.md"))
350
- memory.search_documents("how to configure redis", limit: 3).each do |hit|
351
- puts "#{hit[:key]} (#{hit[:score].round(3)})"
352
- end
353
- ```
354
-
355
- Documents live in a separate store, not in the key-value backend — they do not
356
- appear in `keys`, `all_keys`, or `to_h`.
357
-
358
- ## Reserved Key Accessors
359
-
360
- ### data
361
-
362
- ```ruby
363
- memory.data # => StateProxy
364
- memory.data[:user_id] # Hash access
365
- memory.data.user_id # Method access
366
- memory.data[:status] = "active"
367
- ```
368
-
369
- Runtime data accessed through a [`StateProxy`](state.md) for method-style access.
370
- Writes made through the proxy are **not** reactive — they bypass `set`, so they
371
- wake no blocking readers and notify no subscribers.
372
-
373
- ### results
374
-
375
- ```ruby
376
- memory.results # => Array<RobotResult>
377
- ```
378
-
379
- Accumulated robot results (returns a copy).
380
-
381
- ### messages
382
-
383
- ```ruby
384
- memory.messages # => Array<Message>
385
- ```
386
-
387
- Conversation messages (returns a copy).
388
-
389
- ### session_id
390
-
391
- ```ruby
392
- memory.session_id # => String | nil
393
- memory.session_id = "abc" # Set session identifier
394
- ```
395
-
396
- ### cache
397
-
398
- ```ruby
399
- memory.cache # => RubyLLM::SemanticCache (the module itself), or nil
400
- ```
401
-
402
- Read-only after initialization — assigning it raises
403
- `ArgumentError: Cannot reassign cache - it is initialized automatically`.
404
-
405
- !!! warning "`cache` is `nil` when caching is disabled"
406
- The value stored is the `RubyLLM::SemanticCache` **module**, not an instance.
407
- When constructed with `enable_cache: false` it is `nil`, so
408
- `memory.cache.fetch(...)` raises `NoMethodError`. Guard on
409
- `memory.cache` before use, or leave `enable_cache` at its `true` default.
410
-
411
- ```ruby
412
- RobotLab::Memory.new.cache # => RubyLLM::SemanticCache
413
- RobotLab::Memory.new(enable_cache: false).cache # => nil
414
- ```
415
-
416
- ## Serialization
417
-
418
- ### to_h
419
-
420
- ```ruby
421
- memory.to_h
422
- # => { data: {...}, results: [...], messages: [...], session_id: "...", custom: {...} }
423
- ```
424
-
425
- Keys: `data` (from the `StateProxy`), `results` (each via `RobotResult#export`),
426
- `messages` (each via `to_h`), `session_id`, and `custom` (every non-reserved key).
427
- `cache` is never serialized.
428
-
429
- !!! warning "`to_h` is `.compact`ed"
430
- Nil values are dropped, so `session_id` disappears entirely when unset:
431
-
432
- ```ruby
433
- RobotLab::Memory.new.to_h
434
- # => { data: {}, results: [], messages: [], custom: {} } -- no :session_id
435
- ```
436
-
437
- Consumers must tolerate the missing key. `custom` is always present (it is
438
- `{}` rather than nil when there are no custom keys).
439
-
440
- ### to_json
441
-
442
- ```ruby
443
- memory.to_json # => String
444
- ```
445
-
446
- Serializes `to_h`.
447
-
448
- ### from_hash
449
-
450
- ```ruby
451
- memory = RobotLab::Memory.from_hash(hash)
452
- ```
453
-
454
- Reconstructs `data`, `results`, `messages`, and `session_id`, then re-applies
455
- every entry from `custom`. A fresh cache is created; subscriptions, backend
456
- choice, and `network_name` are **not** restored.
457
-
458
- ### clone / dup
459
-
460
- ```ruby
461
- new_memory = memory.clone
462
- new_memory = memory.dup # alias for clone
463
- ```
464
-
465
- Deep copy of `data` plus copies of `results`, `messages`, `session_id`, and all
466
- non-reserved keys. The `enable_cache` setting and `network_name` are preserved.
467
- Subscriptions are **not** copied — the clone starts with none. Copying custom
468
- keys uses the internal non-reactive setter, so no notifications fire.
469
-
470
- ## Examples
471
-
472
- ### Basic Usage
473
-
474
- ```ruby
475
- robot.memory.set(:user_name, "Alice")
476
- robot.memory.set(:order_count, 5)
477
-
478
- name = robot.memory.get(:user_name) # => "Alice"
479
- count = robot.memory.get(:order_count) # => 5
480
- ```
481
-
482
- ### Bracket Access
483
-
484
- ```ruby
485
- robot.memory[:user_id] = 123
486
- robot.memory[:user_id] # => 123
487
- ```
488
-
489
- ### Storing Objects
490
-
491
- ```ruby
492
- robot.memory.set(:user, {
493
- id: 123,
494
- name: "Alice",
495
- plan: "pro"
496
- })
497
-
498
- user = robot.memory.get(:user)
499
- user[:plan] # => "pro"
500
- ```
501
-
502
- ### Blocking Reads (Network Parallel Execution)
503
-
504
- ```ruby
505
- # In robot A (writer)
506
- network.memory.set(:sentiment, { score: 0.8, confidence: 0.95 })
507
-
508
- # In robot B (reader, may run concurrently)
509
- result = network.memory.get(:sentiment, wait: true) # Block indefinitely
510
- result = network.memory.get(:sentiment, wait: 30) # Block up to 30s, then raise
511
-
512
- # Multiple keys — the timeout applies PER MISSING KEY, so this can block 180s
513
- results = network.memory.get(:sentiment, :entities, :keywords, wait: 60)
514
- # => { sentiment: {...}, entities: [...], keywords: [...] }
515
-
516
- # Treat a timeout as "not available"
517
- sentiment = begin
518
- network.memory.get(:sentiment, wait: 30)
519
- rescue RobotLab::AwaitTimeout
520
- nil
521
- end
522
- ```
523
-
524
- ### Reactive Subscriptions
525
-
526
- ```ruby
527
- # Subscribe to a key
528
- memory.subscribe(:raw_data) do |change|
529
- enriched = enrich(change.value)
530
- memory.set(:enriched, enriched)
531
- end
532
-
533
- # Subscribe with pattern
534
- memory.subscribe_pattern("user:*") do |change|
535
- puts "User key #{change.key} updated by #{change.writer}"
536
- end
537
- ```
538
-
539
- ### Cross-Robot Communication via Network Memory
540
-
541
- ```ruby
542
- # In classifier robot
543
- network.memory.set(:intent, "billing")
544
- network.memory.set(:entities, ["order", "refund"])
545
-
546
- # In handler robot
547
- intent = network.memory.get(:intent)
548
- entities = network.memory.get(:entities)
549
- ```
550
-
551
- ### Data Proxy
552
-
553
- ```ruby
554
- memory = RobotLab.create_memory(
555
- data: { user: { name: "Alice", plan: "pro" } }
556
- )
557
-
558
- memory.data[:user][:name] # => "Alice"
559
- memory.data.to_h # => { user: { name: "Alice", plan: "pro" } }
560
- ```
561
-
562
- ### Serialization
563
-
564
- ```ruby
565
- # Save memory
566
- json = memory.to_json
567
- File.write("memory.json", json)
568
-
569
- # Restore memory
570
- data = JSON.parse(File.read("memory.json"))
571
- memory = Memory.from_hash(data)
572
- ```
573
-
574
- ## See Also
575
-
576
- - [Memory Guide](../../guides/memory.md)
577
- - [StateProxy](state.md) — the wrapper returned by `memory.data`
578
- - [State Management Architecture](../../architecture/state-management.md)