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,258 +0,0 @@
1
- # Basic Chat
2
-
3
- A simple conversational robot example.
4
-
5
- ## Overview
6
-
7
- This example demonstrates the minimal setup for a conversational robot that can respond to user messages using `robot.run("message")`.
8
-
9
- The closest runnable file in this repository is
10
- [`examples/01_simple_robot.rb`](https://github.com/MadBomber/robot_lab/blob/main/examples/01_simple_robot.rb).
11
- The snippets below wrap that same API in a small REPL.
12
-
13
- ## Complete Example
14
-
15
- ```ruby
16
- #!/usr/bin/env ruby
17
-
18
- require "bundler/setup"
19
- require "robot_lab"
20
-
21
- # Build a simple assistant
22
- assistant = RobotLab.build(
23
- name: "assistant",
24
- description: "A helpful conversational assistant",
25
- system_prompt: <<~PROMPT,
26
- You are a helpful, friendly assistant. You provide clear,
27
- concise answers to questions. Be conversational but informative.
28
- PROMPT
29
- model: "claude-sonnet-4"
30
- )
31
-
32
- # Simple REPL
33
- puts "Chat with the assistant (type 'quit' to exit)"
34
- puts "-" * 50
35
-
36
- loop do
37
- print "\nYou: "
38
- input = gets&.chomp
39
-
40
- break if input.nil? || input.downcase == "quit"
41
- next if input.empty?
42
-
43
- # Run the robot with the user's message
44
- result = assistant.run(input)
45
-
46
- # Display response
47
- puts "\nAssistant: #{result.last_text_content}"
48
- end
49
-
50
- puts "\nGoodbye!"
51
- ```
52
-
53
- ## With Streaming
54
-
55
- Pass a block to `run` to receive each `RubyLLM::Chunk` as it arrives.
56
-
57
- > [!WARNING]
58
- > The yielded object is a `RubyLLM::Chunk`. Read its text with `chunk.content`.
59
- > There is no `chunk.text` method — a guard like `if chunk.respond_to?(:text)`
60
- > silently prints nothing.
61
-
62
- ```ruby
63
- #!/usr/bin/env ruby
64
-
65
- require "bundler/setup"
66
- require "robot_lab"
67
-
68
- assistant = RobotLab.build(
69
- name: "assistant",
70
- system_prompt: "You are a helpful assistant.",
71
- model: "claude-sonnet-4"
72
- )
73
-
74
- puts "Chat with streaming (type 'quit' to exit)"
75
- puts "-" * 50
76
-
77
- loop do
78
- print "\nYou: "
79
- input = gets&.chomp
80
-
81
- break if input.nil? || input.downcase == "quit"
82
- next if input.empty?
83
-
84
- print "\nAssistant: "
85
- assistant.run(input) { |chunk| print chunk.content }
86
- puts
87
- end
88
-
89
- puts "\nGoodbye!"
90
- ```
91
-
92
- You can also wire streaming once at build time with the `on_content:` callback,
93
- which fires on every `run`:
94
-
95
- ```ruby
96
- assistant = RobotLab.build(
97
- name: "assistant",
98
- system_prompt: "You are a helpful assistant.",
99
- on_content: ->(chunk) { print chunk.content }
100
- )
101
-
102
- assistant.run("Tell me a one-sentence fact about Ruby.")
103
- ```
104
-
105
- When both are supplied, the stored `on_content` callback fires first, then the
106
- block. See [`examples/05_streaming.rb`](https://github.com/MadBomber/robot_lab/blob/main/examples/05_streaming.rb)
107
- for all four variations (stored callback, per-call block, both, and via `RunConfig`).
108
-
109
- ## With Template
110
-
111
- Templates are `.md` files with YAML front matter, resolved from the configured
112
- prompts directory (`ROBOT_LAB_TEMPLATE_PATH`). Parameters declared `null` in the
113
- front matter are required and are supplied via `context:`.
114
-
115
- ```ruby
116
- #!/usr/bin/env ruby
117
-
118
- require "bundler/setup"
119
- require "robot_lab"
120
-
121
- # Template file: prompts/support.md
122
- # ---
123
- # description: Support assistant
124
- # parameters:
125
- # company_name: null
126
- # tone: friendly
127
- # ---
128
- # You are a <%= tone %> support assistant for <%= company_name %>.
129
-
130
- assistant = RobotLab.build(
131
- name: "assistant",
132
- template: :support,
133
- context: { company_name: "Acme Corp", tone: "friendly" },
134
- model: "claude-sonnet-4"
135
- )
136
-
137
- puts "Chat with template-based assistant (type 'quit' to exit)"
138
- puts "-" * 50
139
-
140
- loop do
141
- print "\nYou: "
142
- input = gets&.chomp
143
-
144
- break if input.nil? || input.downcase == "quit"
145
- next if input.empty?
146
-
147
- result = assistant.run(input)
148
- puts "\nAssistant: #{result.last_text_content}"
149
- end
150
-
151
- puts "\nGoodbye!"
152
- ```
153
-
154
- A full template-driven network lives in
155
- [`examples/06_prompt_templates.rb`](https://github.com/MadBomber/robot_lab/blob/main/examples/06_prompt_templates.rb).
156
-
157
- ## With Memory
158
-
159
- ```ruby
160
- #!/usr/bin/env ruby
161
-
162
- require "bundler/setup"
163
- require "robot_lab"
164
-
165
- assistant = RobotLab.build(
166
- name: "assistant",
167
- system_prompt: "You are a helpful assistant. Use the user's name when you know it.",
168
- model: "claude-sonnet-4"
169
- )
170
-
171
- puts "Chat with memory (type 'quit' to exit)"
172
- puts "-" * 50
173
-
174
- # Store user info in the robot's inherent memory
175
- assistant.memory[:user_name] = "Alice"
176
-
177
- loop do
178
- print "\nYou: "
179
- input = gets&.chomp
180
-
181
- break if input.nil? || input.downcase == "quit"
182
- next if input.empty?
183
-
184
- # The robot's persistent @chat maintains conversation history automatically
185
- result = assistant.run(input)
186
- puts "\nAssistant: #{result.last_text_content}"
187
- end
188
-
189
- puts "\nGoodbye!"
190
- ```
191
-
192
- The full Memory API — subscriptions, `StateProxy`, blocking reads, clone and
193
- reset — is demonstrated in
194
- [`examples/10_memory.rb`](https://github.com/MadBomber/robot_lab/blob/main/examples/10_memory.rb).
195
-
196
- ## Bare Robot with Chaining
197
-
198
- ```ruby
199
- #!/usr/bin/env ruby
200
-
201
- require "bundler/setup"
202
- require "robot_lab"
203
-
204
- # Build a bare robot with no template or prompt
205
- robot = RobotLab.build(name: "bot")
206
-
207
- # Configure via chaining
208
- result = robot
209
- .with_model("claude-sonnet-4")
210
- .with_temperature(0.7)
211
- .with_instructions("You are a pirate. Respond in pirate speak.")
212
- .run("What is the weather like today?")
213
-
214
- puts result.last_text_content
215
- ```
216
-
217
- > [!NOTE]
218
- > Only the `with_*` methods that `RubyLLM::Chat` exposes are delegated:
219
- > `with_context`, `with_headers`, `with_instructions`, `with_model`, `with_params`,
220
- > `with_schema`, `with_temperature`, `with_thinking`, `with_tool`, `with_tools`
221
- > (plus RobotLab's own `with_template` and `with_bus`).
222
- > There is no `with_max_tokens` / `with_top_p` / `with_top_k` — use a constructor
223
- > kwarg (`max_tokens: 2000`) or `with_params(max_tokens: 2000, top_p: 0.3)`.
224
-
225
- `examples/09_chaining.rb` walks through chaining and reconfiguration without
226
- making any LLM calls.
227
-
228
- ## Running
229
-
230
- ```bash
231
- # Set API key
232
- export ANTHROPIC_API_KEY="your-key"
233
-
234
- # Simplest runnable robot
235
- ruby examples/01_simple_robot.rb
236
-
237
- # Streaming
238
- ruby examples/05_streaming.rb
239
-
240
- # with_* chaining and reconfiguration (no LLM calls)
241
- ruby examples/09_chaining.rb
242
- ```
243
-
244
- ## Key Concepts
245
-
246
- 1. **Robot Building**: Use `RobotLab.build(name:, system_prompt:)` or `RobotLab.build(name:, template:)` to create a robot
247
- 2. **Execution**: Call `robot.run("message")` to send a message and get a response
248
- 3. **Response**: Access the text via `result.last_text_content` (aliased as `result.reply`)
249
- 4. **Streaming**: Pass a block to `robot.run("message") { |chunk| print chunk.content }`, or set `on_content:` at build time
250
- 5. **Memory**: Access inherent memory via `robot.memory[:key]`
251
- 6. **Chaining**: Configure with the delegated `with_*` methods, which return `self`
252
- 7. **Conversation History**: The persistent `@chat` maintains history across multiple `run` calls
253
-
254
- ## See Also
255
-
256
- - [Building Robots Guide](../guides/building-robots.md)
257
- - [Streaming Guide](../guides/streaming.md)
258
- - [Robot API Reference](../api/core/robot.md)
@@ -1,425 +0,0 @@
1
- # Examples
2
-
3
- Complete working examples demonstrating RobotLab features.
4
-
5
- ## Overview
6
-
7
- These examples show how to use RobotLab for common scenarios, from simple chatbots to complex multi-robot systems.
8
-
9
- ## Examples
10
-
11
- | Example | Description |
12
- |---------|-------------|
13
- | [Basic Chat](basic-chat.md) | Simple conversational robot |
14
- | [Multi-Robot Network](multi-robot-network.md) | Customer service with routing |
15
- | [Tool Usage](tool-usage.md) | External API integration |
16
- | [MCP Server](mcp-server.md) | Connecting a robot to external MCP servers |
17
- | [Message Bus](#message-bus) | Bidirectional robot communication with convergence |
18
- | [Spawning Robots](#spawning-robots) | Dynamic specialist creation at runtime |
19
-
20
- > **Rails example** — see [robot_lab-rails](https://github.com/MadBomber/robot_lab-rails/blob/main/docs/examples/rails-application.md) for a full Rails integration example.
21
-
22
- ## Quick Links
23
-
24
- ### Simple Examples
25
-
26
- - [Hello World Robot](#hello-world)
27
- - [Robot with Tools](#robot-with-tools)
28
- - [Network with Routing](#network-with-routing)
29
-
30
- ### Advanced Examples
31
-
32
- - [Streaming Responses](basic-chat.md#with-streaming)
33
- - [Persistent Conversations](basic-chat.md#with-memory)
34
- - [MCP Integration](mcp-server.md)
35
- - [Message Bus Communication](#message-bus)
36
- - [Spawning Robots](#spawning-robots)
37
-
38
- ## Hello World
39
-
40
- ```ruby
41
- require "robot_lab"
42
-
43
- # Configuration is handled automatically via MywayConfig.
44
- # Set API keys via environment variables:
45
- # ROBOT_LAB_RUBY_LLM__ANTHROPIC_API_KEY=sk-ant-...
46
- # Or via config files (~/.config/robot_lab/robot_lab.yml — the filename
47
- # repeats the app name; ~/.config/robot_lab/config.yml is never read)
48
-
49
- robot = RobotLab.build(
50
- name: "greeter",
51
- system_prompt: "You are a friendly greeter. Say hello warmly."
52
- )
53
-
54
- result = robot.run("Hi there!")
55
-
56
- puts result.last_text_content
57
- ```
58
-
59
- ## Robot with Tools
60
-
61
- Give the LLM a fixed set of operations rather than an expression evaluator —
62
- never `eval` a string the model produced.
63
-
64
- ```ruby
65
- class CalculatorTool < RubyLLM::Tool
66
- description "Performs basic arithmetic operations"
67
-
68
- param :operation, type: "string", desc: "add, subtract, multiply, or divide"
69
- param :a, type: "number", desc: "First operand"
70
- param :b, type: "number", desc: "Second operand"
71
-
72
- def execute(operation:, a:, b:)
73
- case operation
74
- when "add" then a + b
75
- when "subtract" then a - b
76
- when "multiply" then a * b
77
- when "divide" then a.to_f / b
78
- else "Unknown operation: #{operation}"
79
- end
80
- end
81
- end
82
-
83
- robot = RobotLab.build(
84
- name: "calculator",
85
- system_prompt: "You help with calculations.",
86
- local_tools: [CalculatorTool]
87
- )
88
-
89
- # tools: :inherit is REQUIRED — run() defaults to tools: :none
90
- result = robot.run("What's 25 * 4?", tools: :inherit)
91
- puts result.last_text_content
92
- ```
93
-
94
- > [!WARNING]
95
- > `Robot#run` defaults to `tools: :none` and `mcp: :none`. A plain
96
- > `robot.run("...")` sends the LLM **no tools at all**, even when `local_tools:`
97
- > were attached at build time. Pass `tools: :inherit` on the call that should be
98
- > able to use them.
99
- >
100
- > For a **standalone** robot like this one, do not pass `tools: :inherit` at
101
- > *build* time — there the parent level is the global default (`:none`), so it
102
- > produces an allowlist that matches nothing. Leave `tools:` unset in the
103
- > constructor. (Inside a network the opposite holds: build-time `:inherit` is how a
104
- > robot opts into the allowlist on the network's `config:`. See
105
- > [Configuration](../getting-started/configuration.md#hierarchical-mcp-and-tools).)
106
-
107
- See [`examples/02_tools.rb`](https://github.com/MadBomber/robot_lab/blob/main/examples/02_tools.rb)
108
- for a second tool definition (`FortuneCookie`) alongside the calculator.
109
-
110
- ## Network with Routing
111
-
112
- Routing is not configured declaratively — a robot performs it. Subclass
113
- `RobotLab::Robot`, override `#call`, and activate one of the `depends_on: :optional`
114
- tasks based on what the LLM returned.
115
-
116
- ```ruby
117
- class ClassifierRobot < RobotLab::Robot
118
- def call(result)
119
- run_context = extract_run_context(result)
120
- message = run_context.delete(:message)
121
- robot_result = run(message, **run_context)
122
-
123
- new_result = result
124
- .with_context(@name.to_sym, robot_result)
125
- .continue(robot_result)
126
-
127
- case robot_result.reply.to_s.strip.downcase
128
- when /billing/ then new_result.activate(:billing)
129
- when /technical/ then new_result.activate(:technical)
130
- else new_result.activate(:general)
131
- end
132
- end
133
- end
134
-
135
- classifier = ClassifierRobot.new(
136
- name: "classifier",
137
- system_prompt: "Classify the request as BILLING, TECHNICAL, or GENERAL. Respond with only the category."
138
- )
139
-
140
- billing = RobotLab.build(name: "billing", system_prompt: "You handle billing questions.")
141
- tech = RobotLab.build(name: "technical", system_prompt: "You handle technical issues.")
142
- general = RobotLab.build(name: "general", system_prompt: "You handle everything else.")
143
-
144
- network = RobotLab.create_network(name: "support") do
145
- task :classifier, classifier, depends_on: :none
146
- task :billing, billing, depends_on: :optional
147
- task :technical, tech, depends_on: :optional
148
- task :general, general, depends_on: :optional
149
- end
150
-
151
- result = network.run(message: "I was charged twice for my subscription")
152
-
153
- # Access individual robot results via context
154
- puts result.context[:classifier].last_text_content
155
- puts result.value.last_text_content # the specialist that was activated
156
- ```
157
-
158
- > [!WARNING]
159
- > `result.context` is keyed by the **robot's** name (`with_context(@name.to_sym, ...)`),
160
- > not the task name, and `activate(:name)` takes a **task** name. Keep the two
161
- > identical — activating a task name that was never declared **raises
162
- > `ArgumentError` and aborts the run** (`Step :classifier attempted to activate
163
- > unknown step :billing`). The same happens for a task that was declared without
164
- > `depends_on: :optional`.
165
-
166
- Full version: [`examples/03_network.rb`](https://github.com/MadBomber/robot_lab/blob/main/examples/03_network.rb).
167
-
168
- ## Chaining Configuration
169
-
170
- Robots support `with_*` methods that return `self` for chaining:
171
-
172
- ```ruby
173
- robot = RobotLab.build(name: "assistant")
174
- .with_instructions("You are a helpful coding assistant.")
175
- .with_temperature(0.3)
176
- .with_model("gpt-4o")
177
-
178
- result = robot.run("Explain Ruby blocks.")
179
- puts result.last_text_content
180
- ```
181
-
182
- > [!NOTE]
183
- > The chainable set is exactly what `RubyLLM::Chat` exposes — `with_context`,
184
- > `with_headers`, `with_instructions`, `with_model`, `with_params`, `with_schema`,
185
- > `with_temperature`, `with_thinking`, `with_tool`, `with_tools` — plus RobotLab's
186
- > `with_template` and `with_bus`. There is no `with_max_tokens`, `with_top_p`, or
187
- > `with_top_k`; use a constructor kwarg or `with_params(max_tokens: 2000)`.
188
-
189
- ## Using Templates
190
-
191
- Templates are `.md` files with optional YAML front matter, managed by prompt_manager.
192
- The body is rendered with **ERB** — interpolate with `<%= var %>`.
193
-
194
- ```ruby
195
- # Template file: prompts/support.md
196
- # ---
197
- # model: claude-sonnet-4
198
- # temperature: 0.5
199
- # parameters:
200
- # company_name: null
201
- # ---
202
- # You are a support assistant for <%= company_name %>.
203
-
204
- robot = RobotLab.build(
205
- name: "support",
206
- template: :support,
207
- context: { company_name: "Acme Corp" }
208
- )
209
-
210
- result = robot.run("How do I reset my password?")
211
- puts result.last_text_content
212
- ```
213
-
214
- > [!NOTE]
215
- > `{{ var }}` is **not** interpolated — it passes through to the LLM verbatim.
216
- > Of the LLM keys accepted in front matter, only `model:` and `temperature:` are
217
- > actually applied to the chat. `top_p`, `top_k`, `max_tokens`, `presence_penalty`,
218
- > `frequency_penalty`, and `stop` are parsed and then silently dropped; supply
219
- > those as constructor kwargs or via a `RunConfig` instead.
220
-
221
- ## Running Examples
222
-
223
- 1. Install dependencies:
224
- ```bash
225
- bundle install
226
- ```
227
-
228
- 2. Set API key:
229
- ```bash
230
- export ANTHROPIC_API_KEY="your-key"
231
- ```
232
-
233
- 3. Run example:
234
- ```bash
235
- ruby examples/01_simple_robot.rb
236
- ```
237
-
238
- Or use the provided rake tasks:
239
-
240
- ```bash
241
- bundle exec rake examples:all # Run all examples
242
- bundle exec rake examples:run[1] # Run specific example by number
243
- ```
244
-
245
- ## Shared Example Setup (`examples/common.rb`)
246
-
247
- Most numbered examples (`01_*.rb` through `35_*.rb`) pull in a shared setup file:
248
-
249
- ```ruby
250
- require_relative "common"
251
- ```
252
-
253
- The line sits below each example's header comment rather than at the very top of
254
- the file, and three examples do without it entirely —
255
- `32_newsletter_reader.rb`, `33_stock_generator.rb`, and `33_stock_predictor.rb`.
256
-
257
- `common.rb` handles the shared boilerplate so individual examples stay focused:
258
-
259
- - **`LLM` hash** — frozen lookup of provider/model pairs accessible as `LLM[:default]`, `LLM[:local]`, `LLM[:anthropic]`. Each entry is a `LlmConfig = Data.define(:provider, :model)` value, so you access the model string as `LLM[:default].model`.
260
- - **`RubyLLM.configure`** — sets a null logger and `LLM[:default].model` as the `default_model`.
261
- - **`RobotLab.configure`** — sets a null logger.
262
- - **Output helpers** — `banner(title)`, `section(title)`, `hr`, and `show_code(ruby_string, label:)` (Rouge-highlighted) for consistent terminal formatting.
263
-
264
- ## Template Path via direnv
265
-
266
- Examples that bundle their own `prompts/` directory ship with a `.envrc` file:
267
-
268
- ```
269
- examples/.envrc
270
- examples/14_rusty_circuit/.envrc
271
- examples/15_memory_network_and_bus/.envrc
272
- examples/16_writers_room/.envrc
273
- ```
274
-
275
- Each sets `ROBOT_LAB_TEMPLATE_PATH` to the local `prompts/` directory when [direnv](https://direnv.net/) is active. `common.rb` also sets this variable as a fallback if `direnv` has not loaded the `.envrc`:
276
-
277
- ```ruby
278
- ENV["ROBOT_LAB_TEMPLATE_PATH"] ||= File.join(__dir__, "prompts")
279
- ```
280
-
281
- This means examples work correctly whether you run them from the project root with rake tasks or directly from inside the example's own directory.
282
-
283
- ## Message Bus
284
-
285
- Robots can communicate bidirectionally via a message bus, enabling convergence loops and negotiation patterns. This example demonstrates a comedy critic tasking a comedian to generate jokes until one passes:
286
-
287
- ```ruby
288
- ENV['ROBOT_LAB_TEMPLATE_PATH'] ||= File.join(__dir__, "prompts")
289
- require "robot_lab"
290
-
291
- MAX_ATTEMPTS = 5
292
-
293
- class Comedian < RobotLab::Robot
294
- TEMP_START = 0.2
295
- TEMP_STEP = 0.2
296
-
297
- def initialize(bus:)
298
- super(name: "bob", template: :comedian, bus: bus, temperature: TEMP_START)
299
- @attempts = 0
300
- on_message do |message|
301
- @attempts += 1
302
- temp = [TEMP_START + TEMP_STEP * (@attempts - 1), 1.0].min
303
- with_temperature(temp)
304
- joke = run(message.content.to_s).reply.strip
305
- send_reply(to: message.from.to_sym, content: joke, in_reply_to: message.key)
306
- end
307
- end
308
-
309
- attr_reader :attempts
310
- end
311
-
312
- class ComedyCritic < RobotLab::Robot
313
- def initialize(bus:)
314
- super(name: "alice", template: :comedy_critic, bus: bus)
315
- @accepted = false
316
- @rounds = 0
317
- on_message do |message|
318
- @rounds += 1
319
- verdict = run("Evaluate this joke:\n\n#{message.content}").reply.strip
320
- @accepted = verdict.start_with?("FUNNY")
321
- # The @rounds guard is what terminates the loop — without it the critic
322
- # keeps sending Bob back forever.
323
- send_message(to: :bob, content: "Not funny enough. Try again.") unless @accepted || @rounds >= MAX_ATTEMPTS
324
- end
325
- end
326
-
327
- attr_reader :accepted
328
- end
329
-
330
- bus = TypedBus::MessageBus.new
331
- bob = Comedian.new(bus: bus)
332
- alice = ComedyCritic.new(bus: bus)
333
-
334
- alice.send_message(to: :bob, content: "Tell me a funny robot joke.")
335
- puts "Attempts: #{bob.attempts} / #{MAX_ATTEMPTS}"
336
- puts "Accepted: #{alice.accepted}"
337
- ```
338
-
339
- Key patterns demonstrated:
340
-
341
- - **Robot subclasses** with templates for prompt management
342
- - **Auto-ack** via 1-arg `on_message` blocks
343
- - **`send_reply(to:, content:, in_reply_to:)`** for correlated responses
344
- - **Temperature ramping** (0.2 &rarr; 1.0) for increasing creativity
345
- - **Convergence loop** that terminates when the critic approves *or* `MAX_ATTEMPTS` is reached
346
-
347
- > [!WARNING]
348
- > `MAX_ATTEMPTS` only bounds the loop because the critic checks it before sending
349
- > Bob back. Declaring the constant without testing it leaves the two robots
350
- > messaging each other indefinitely.
351
-
352
- Run: `bundle exec ruby examples/12_message_bus.rb`
353
-
354
- ## Spawning Robots
355
-
356
- Robots can create new specialist robots at runtime using `spawn`. A dispatcher receives questions, decides what kind of specialist is needed, and spawns one on the fly. The bus is created lazily — no explicit setup required:
357
-
358
- ```ruby
359
- ENV['ROBOT_LAB_TEMPLATE_PATH'] ||= File.join(__dir__, "prompts")
360
- require "robot_lab"
361
-
362
- QUESTIONS = [
363
- "Why did the Roman Empire fall?",
364
- "Write a haiku about recursion.",
365
- "What is the square root of 144?",
366
- ].freeze
367
-
368
- class Dispatcher < RobotLab::Robot
369
- attr_reader :spawned
370
-
371
- def initialize(bus: nil)
372
- super(name: "dispatcher", template: :dispatcher, bus: bus)
373
- @spawned = {}
374
- @pending = {}
375
-
376
- on_message do |message|
377
- puts " Dispatcher <- :#{message.from} replied"
378
- puts " | #{message.content.to_s.lines.first&.strip}"
379
- @pending.delete(message.from)
380
- end
381
- end
382
-
383
- def dispatch(question)
384
- plan = run(question).last_text_content.strip
385
- role, instruction = plan.split("\n", 2)
386
- role = role.strip.downcase.gsub(/\s+/, "_")
387
- instruction = instruction&.strip || "You are a helpful #{role}."
388
-
389
- specialist = @spawned[role] ||= spawn(
390
- name: role,
391
- system_prompt: instruction
392
- )
393
-
394
- @pending[role] = question
395
-
396
- specialist.send_message(to: :dispatcher, content:
397
- specialist.run(question).last_text_content.strip
398
- )
399
- end
400
- end
401
-
402
- dispatcher = Dispatcher.new
403
-
404
- QUESTIONS.each_with_index do |question, i|
405
- puts "\nQuestion #{i + 1}: #{question}"
406
- dispatcher.dispatch(question)
407
- end
408
-
409
- puts "\nSpecialists spawned: #{dispatcher.spawned.keys.join(', ')}"
410
- ```
411
-
412
- Key patterns demonstrated:
413
-
414
- - **`spawn`** for dynamic robot creation (bus created lazily)
415
- - **`on_message`** for reply handling
416
- - **LLM-driven delegation** — the dispatcher asks its LLM what specialist to create
417
- - **Specialist reuse** — spawned robots are cached and reused across questions
418
-
419
- Run: `bundle exec ruby examples/13_spawn.rb`
420
-
421
- ## See Also
422
-
423
- - [Getting Started](../getting-started/index.md)
424
- - [Guides](../guides/index.md)
425
- - [API Reference](../api/index.md)