robot_lab 0.3.0 → 0.3.1

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