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,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)