robot_lab 0.0.12 → 0.2.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 (243) hide show
  1. checksums.yaml +4 -4
  2. data/.architecture/AGENTS.md +32 -0
  3. data/.architecture/config.yml +8 -0
  4. data/.architecture/members.yml +60 -0
  5. data/.architecture/reviews/feature-free-will.md +490 -0
  6. data/.architecture/reviews/overall-codebase.md +427 -0
  7. data/.claude/settings.local.json +57 -0
  8. data/.codex/config.toml +2 -0
  9. data/.irbrc +2 -2
  10. data/.rubocop.yml +172 -0
  11. data/CHANGELOG.md +108 -0
  12. data/CLAUDE.md +139 -0
  13. data/README.md +91 -45
  14. data/Rakefile +109 -3
  15. data/agent2agent_review.md +192 -0
  16. data/agentf_improvements.md +253 -0
  17. data/agents.md +14 -0
  18. data/docs/api/messages/index.md +21 -0
  19. data/docs/examples/index.md +37 -2
  20. data/docs/getting-started/configuration.md +20 -7
  21. data/docs/guides/creating-networks.md +23 -0
  22. data/docs/guides/index.md +16 -16
  23. data/docs/guides/knowledge.md +7 -1
  24. data/docs/guides/observability.md +132 -0
  25. data/docs/index.md +30 -3
  26. data/docs/superpowers/plans/2026-05-06-agentskills.md +1303 -0
  27. data/docs/superpowers/specs/2026-05-06-agentskills-design.md +247 -0
  28. data/examples/.envrc +1 -0
  29. data/examples/01_simple_robot.rb +5 -9
  30. data/examples/02_tools.rb +5 -9
  31. data/examples/03_network.rb +8 -9
  32. data/examples/04_mcp.rb +21 -29
  33. data/examples/05_streaming.rb +12 -18
  34. data/examples/06_prompt_templates.rb +11 -19
  35. data/examples/07_network_memory.rb +16 -31
  36. data/examples/08_llm_config.rb +10 -22
  37. data/examples/09_chaining.rb +16 -27
  38. data/examples/10_memory.rb +12 -28
  39. data/examples/11_network_introspection.rb +15 -29
  40. data/examples/12_message_bus.rb +5 -12
  41. data/examples/13_spawn.rb +5 -10
  42. data/examples/14_rusty_circuit/.envrc +1 -0
  43. data/examples/14_rusty_circuit/comic.rb +2 -0
  44. data/examples/14_rusty_circuit/heckler.rb +1 -1
  45. data/examples/14_rusty_circuit/open_mic.rb +1 -3
  46. data/examples/14_rusty_circuit/scout.rb +2 -0
  47. data/examples/15_memory_network_and_bus/.envrc +1 -0
  48. data/examples/15_memory_network_and_bus/editorial_pipeline.rb +6 -3
  49. data/examples/15_memory_network_and_bus/linux_writer.rb +1 -1
  50. data/examples/15_memory_network_and_bus/output/combined_article.md +6 -6
  51. data/examples/15_memory_network_and_bus/output/final_article.md +6 -8
  52. data/examples/15_memory_network_and_bus/output/linux_draft.md +4 -2
  53. data/examples/15_memory_network_and_bus/output/mac_draft.md +3 -3
  54. data/examples/15_memory_network_and_bus/output/memory.json +6 -6
  55. data/examples/15_memory_network_and_bus/output/revision_1.md +10 -11
  56. data/examples/15_memory_network_and_bus/output/revision_2.md +6 -8
  57. data/examples/15_memory_network_and_bus/output/windows_draft.md +3 -3
  58. data/examples/16_writers_room/.envrc +1 -0
  59. data/examples/16_writers_room/writers_room.rb +2 -4
  60. data/examples/17_skills.rb +8 -17
  61. data/examples/18_rails/Gemfile +1 -0
  62. data/examples/18_rails/app/jobs/robot_run_job.rb +15 -75
  63. data/examples/19_token_tracking.rb +9 -15
  64. data/examples/20_circuit_breaker.rb +10 -19
  65. data/examples/21_learning_loop.rb +11 -20
  66. data/examples/22_context_compression.rb +6 -13
  67. data/examples/23_convergence.rb +6 -17
  68. data/examples/24_structured_delegation.rb +11 -15
  69. data/examples/25_history_search.rb +5 -12
  70. data/examples/26_document_store.rb +6 -13
  71. data/examples/27_incident_response/incident_response.rb +4 -5
  72. data/examples/28_mcp_discovery.rb +8 -11
  73. data/examples/29_ractor_tools.rb +4 -9
  74. data/examples/30_ractor_network.rb +10 -19
  75. data/examples/31_launch_assessment.rb +235 -0
  76. data/examples/32_newsletter_reader.rb +188 -0
  77. data/examples/33_stock_generator.rb +80 -0
  78. data/examples/33_stock_predictor.rb +306 -0
  79. data/examples/34_agentskills.rb +72 -0
  80. data/examples/README.md +10 -1
  81. data/examples/common.rb +76 -0
  82. data/examples/ruboruby.md +423 -0
  83. data/examples/temp.md +51 -0
  84. data/lib/robot_lab/agent_skill.rb +63 -0
  85. data/lib/robot_lab/agent_skill_catalog.rb +74 -0
  86. data/lib/robot_lab/ask_user.rb +2 -2
  87. data/lib/robot_lab/bus_poller.rb +12 -5
  88. data/lib/robot_lab/config.rb +1 -12
  89. data/lib/robot_lab/delegation_future.rb +1 -1
  90. data/lib/robot_lab/doom_loop_detector.rb +98 -0
  91. data/lib/robot_lab/history_compressor.rb +4 -10
  92. data/lib/robot_lab/mcp/client.rb +1 -2
  93. data/lib/robot_lab/mcp/connection_poller.rb +3 -3
  94. data/lib/robot_lab/mcp/server.rb +1 -1
  95. data/lib/robot_lab/mcp/server_discovery.rb +0 -2
  96. data/lib/robot_lab/memory.rb +32 -27
  97. data/lib/robot_lab/memory_change.rb +2 -2
  98. data/lib/robot_lab/message.rb +5 -5
  99. data/lib/robot_lab/network.rb +12 -7
  100. data/lib/robot_lab/robot/agent_skill_matching.rb +99 -0
  101. data/lib/robot_lab/robot/bus_messaging.rb +9 -27
  102. data/lib/robot_lab/robot/history_search.rb +4 -1
  103. data/lib/robot_lab/robot/mcp_management.rb +5 -11
  104. data/lib/robot_lab/robot/template_rendering.rb +60 -40
  105. data/lib/robot_lab/robot.rb +323 -206
  106. data/lib/robot_lab/robot_result.rb +6 -5
  107. data/lib/robot_lab/run_config.rb +5 -11
  108. data/lib/robot_lab/script_tool.rb +76 -0
  109. data/lib/robot_lab/state_proxy.rb +7 -5
  110. data/lib/robot_lab/tool.rb +3 -3
  111. data/lib/robot_lab/tool_config.rb +1 -1
  112. data/lib/robot_lab/tool_manifest.rb +5 -7
  113. data/lib/robot_lab/user_message.rb +2 -2
  114. data/lib/robot_lab/version.rb +1 -1
  115. data/lib/robot_lab/waiter.rb +1 -1
  116. data/lib/robot_lab.rb +41 -48
  117. data/logfile +8 -0
  118. data/mkdocs.yml +2 -3
  119. data/robot_concurrency.md +38 -0
  120. data/simple_acp_review.md +298 -0
  121. data/site/404.html +2300 -0
  122. data/site/api/core/index.html +2706 -0
  123. data/site/api/core/memory/index.html +3793 -0
  124. data/site/api/core/network/index.html +3500 -0
  125. data/site/api/core/robot/index.html +4566 -0
  126. data/site/api/core/state/index.html +3390 -0
  127. data/site/api/core/tool/index.html +3843 -0
  128. data/site/api/index.html +2635 -0
  129. data/site/api/mcp/client/index.html +3435 -0
  130. data/site/api/mcp/index.html +2783 -0
  131. data/site/api/mcp/server/index.html +3252 -0
  132. data/site/api/mcp/transports/index.html +3352 -0
  133. data/site/api/messages/index.html +2641 -0
  134. data/site/api/messages/text-message/index.html +3087 -0
  135. data/site/api/messages/tool-call-message/index.html +3159 -0
  136. data/site/api/messages/tool-result-message/index.html +3252 -0
  137. data/site/api/messages/user-message/index.html +3212 -0
  138. data/site/api/streaming/context/index.html +3282 -0
  139. data/site/api/streaming/events/index.html +3347 -0
  140. data/site/api/streaming/index.html +2738 -0
  141. data/site/architecture/core-concepts/index.html +3757 -0
  142. data/site/architecture/index.html +2797 -0
  143. data/site/architecture/message-flow/index.html +3238 -0
  144. data/site/architecture/network-orchestration/index.html +3433 -0
  145. data/site/architecture/robot-execution/index.html +3140 -0
  146. data/site/architecture/state-management/index.html +3498 -0
  147. data/site/assets/css/custom.css +56 -0
  148. data/site/assets/images/favicon.png +0 -0
  149. data/site/assets/images/robot_lab.jpg +0 -0
  150. data/site/assets/javascripts/bundle.79ae519e.min.js +16 -0
  151. data/site/assets/javascripts/bundle.79ae519e.min.js.map +7 -0
  152. data/site/assets/javascripts/lunr/min/lunr.ar.min.js +1 -0
  153. data/site/assets/javascripts/lunr/min/lunr.da.min.js +18 -0
  154. data/site/assets/javascripts/lunr/min/lunr.de.min.js +18 -0
  155. data/site/assets/javascripts/lunr/min/lunr.du.min.js +18 -0
  156. data/site/assets/javascripts/lunr/min/lunr.el.min.js +1 -0
  157. data/site/assets/javascripts/lunr/min/lunr.es.min.js +18 -0
  158. data/site/assets/javascripts/lunr/min/lunr.fi.min.js +18 -0
  159. data/site/assets/javascripts/lunr/min/lunr.fr.min.js +18 -0
  160. data/site/assets/javascripts/lunr/min/lunr.he.min.js +1 -0
  161. data/site/assets/javascripts/lunr/min/lunr.hi.min.js +1 -0
  162. data/site/assets/javascripts/lunr/min/lunr.hu.min.js +18 -0
  163. data/site/assets/javascripts/lunr/min/lunr.hy.min.js +1 -0
  164. data/site/assets/javascripts/lunr/min/lunr.it.min.js +18 -0
  165. data/site/assets/javascripts/lunr/min/lunr.ja.min.js +1 -0
  166. data/site/assets/javascripts/lunr/min/lunr.jp.min.js +1 -0
  167. data/site/assets/javascripts/lunr/min/lunr.kn.min.js +1 -0
  168. data/site/assets/javascripts/lunr/min/lunr.ko.min.js +1 -0
  169. data/site/assets/javascripts/lunr/min/lunr.multi.min.js +1 -0
  170. data/site/assets/javascripts/lunr/min/lunr.nl.min.js +18 -0
  171. data/site/assets/javascripts/lunr/min/lunr.no.min.js +18 -0
  172. data/site/assets/javascripts/lunr/min/lunr.pt.min.js +18 -0
  173. data/site/assets/javascripts/lunr/min/lunr.ro.min.js +18 -0
  174. data/site/assets/javascripts/lunr/min/lunr.ru.min.js +18 -0
  175. data/site/assets/javascripts/lunr/min/lunr.sa.min.js +1 -0
  176. data/site/assets/javascripts/lunr/min/lunr.stemmer.support.min.js +1 -0
  177. data/site/assets/javascripts/lunr/min/lunr.sv.min.js +18 -0
  178. data/site/assets/javascripts/lunr/min/lunr.ta.min.js +1 -0
  179. data/site/assets/javascripts/lunr/min/lunr.te.min.js +1 -0
  180. data/site/assets/javascripts/lunr/min/lunr.th.min.js +1 -0
  181. data/site/assets/javascripts/lunr/min/lunr.tr.min.js +18 -0
  182. data/site/assets/javascripts/lunr/min/lunr.vi.min.js +1 -0
  183. data/site/assets/javascripts/lunr/min/lunr.zh.min.js +1 -0
  184. data/site/assets/javascripts/lunr/tinyseg.js +206 -0
  185. data/site/assets/javascripts/lunr/wordcut.js +6708 -0
  186. data/site/assets/javascripts/workers/search.2c215733.min.js +42 -0
  187. data/site/assets/javascripts/workers/search.2c215733.min.js.map +7 -0
  188. data/site/assets/stylesheets/main.484c7ddc.min.css +1 -0
  189. data/site/assets/stylesheets/main.484c7ddc.min.css.map +1 -0
  190. data/site/assets/stylesheets/palette.ab4e12ef.min.css +1 -0
  191. data/site/assets/stylesheets/palette.ab4e12ef.min.css.map +1 -0
  192. data/site/concepts/index.html +3455 -0
  193. data/site/examples/basic-chat/index.html +2880 -0
  194. data/site/examples/index.html +2907 -0
  195. data/site/examples/mcp-server/index.html +3018 -0
  196. data/site/examples/multi-robot-network/index.html +3131 -0
  197. data/site/examples/rails-application/index.html +3329 -0
  198. data/site/examples/tool-usage/index.html +3085 -0
  199. data/site/getting-started/configuration/index.html +3745 -0
  200. data/site/getting-started/index.html +2572 -0
  201. data/site/getting-started/installation/index.html +2981 -0
  202. data/site/getting-started/quick-start/index.html +2942 -0
  203. data/site/guides/building-robots/index.html +4290 -0
  204. data/site/guides/creating-networks/index.html +3858 -0
  205. data/site/guides/index.html +2586 -0
  206. data/site/guides/mcp-integration/index.html +3581 -0
  207. data/site/guides/memory/index.html +3586 -0
  208. data/site/guides/rails-integration/index.html +4019 -0
  209. data/site/guides/streaming/index.html +3157 -0
  210. data/site/guides/using-tools/index.html +3802 -0
  211. data/site/index.html +2671 -0
  212. data/site/search/search_index.json +1 -0
  213. data/site/sitemap.xml +183 -0
  214. data/site/sitemap.xml.gz +0 -0
  215. data/site/tags.json +1 -0
  216. data/temp.md +6 -0
  217. data/tool_manifest_plan.md +155 -0
  218. metadata +155 -90
  219. data/.github/workflows/deploy-yard-docs.yml +0 -52
  220. data/docs/examples/rails-application.md +0 -419
  221. data/docs/guides/ractor-parallelism.md +0 -364
  222. data/docs/guides/rails-integration.md +0 -642
  223. data/docs/superpowers/plans/2026-04-14-ractor-integration.md +0 -1538
  224. data/docs/superpowers/specs/2026-04-14-ractor-integration-design.md +0 -258
  225. data/lib/generators/robot_lab/install_generator.rb +0 -90
  226. data/lib/generators/robot_lab/robot_generator.rb +0 -55
  227. data/lib/generators/robot_lab/templates/initializer.rb.tt +0 -42
  228. data/lib/generators/robot_lab/templates/job.rb.tt +0 -92
  229. data/lib/generators/robot_lab/templates/migration.rb.tt +0 -32
  230. data/lib/generators/robot_lab/templates/result_model.rb.tt +0 -52
  231. data/lib/generators/robot_lab/templates/robot.rb.tt +0 -31
  232. data/lib/generators/robot_lab/templates/robot_test.rb.tt +0 -34
  233. data/lib/generators/robot_lab/templates/routing_robot.rb.tt +0 -59
  234. data/lib/generators/robot_lab/templates/thread_model.rb.tt +0 -40
  235. data/lib/robot_lab/document_store.rb +0 -155
  236. data/lib/robot_lab/ractor_boundary.rb +0 -42
  237. data/lib/robot_lab/ractor_job.rb +0 -37
  238. data/lib/robot_lab/ractor_memory_proxy.rb +0 -85
  239. data/lib/robot_lab/ractor_network_scheduler.rb +0 -154
  240. data/lib/robot_lab/ractor_worker_pool.rb +0 -117
  241. data/lib/robot_lab/rails_integration/engine.rb +0 -29
  242. data/lib/robot_lab/rails_integration/railtie.rb +0 -42
  243. data/lib/robot_lab/rails_integration/turbo_stream_callbacks.rb +0 -72
@@ -1,642 +0,0 @@
1
- # Rails Integration
2
-
3
- RobotLab integrates seamlessly with Ruby on Rails applications.
4
-
5
- ## Installation
6
-
7
- ### Generate Files
8
-
9
- ```bash
10
- rails generate robot_lab:install
11
- ```
12
-
13
- This creates:
14
-
15
- ```
16
- config/initializers/robot_lab.rb # Logger setup
17
- db/migrate/*_create_robot_lab_tables.rb # Database tables
18
- app/models/robot_lab_thread.rb # Thread model
19
- app/models/robot_lab_result.rb # Result model
20
- app/jobs/robot_run_job.rb # Background job for robot execution
21
- app/robots/ # Directory for robots
22
- app/tools/ # Directory for tools
23
- ```
24
-
25
- Options:
26
-
27
- - `--skip-migration` — Skip database migration generation
28
- - `--skip-job` — Skip background job generation
29
-
30
- ### Run Migrations
31
-
32
- ```bash
33
- rails db:migrate
34
- ```
35
-
36
- ## Configuration
37
-
38
- RobotLab uses [MywayConfig](https://github.com/madbomber/myway_config) for configuration. There is no `RobotLab.configure` block. Instead, settings are loaded from YAML files and environment variables in the following priority order:
39
-
40
- 1. **Bundled defaults** (`lib/robot_lab/config/defaults.yml`)
41
- 2. **Environment-specific overrides** (development, test, production sections)
42
- 3. **XDG user config** (`~/.config/robot_lab/config.yml`)
43
- 4. **Project config** (`./config/robot_lab.yml`)
44
- 5. **Environment variables** (`ROBOT_LAB_*` prefix)
45
-
46
- ### Project Config File
47
-
48
- ```yaml title="config/robot_lab.yml"
49
- defaults:
50
- ruby_llm:
51
- anthropic_api_key: <%= ENV['ANTHROPIC_API_KEY'] %>
52
- openai_api_key: <%= ENV['OPENAI_API_KEY'] %>
53
- model: claude-sonnet-4
54
- request_timeout: 180
55
-
56
- # Template path auto-detected as app/prompts in Rails
57
- # template_path: app/prompts
58
-
59
- development:
60
- ruby_llm:
61
- model: claude-haiku-3
62
- log_level: :debug
63
-
64
- test:
65
- streaming_enabled: false
66
- ruby_llm:
67
- model: claude-3-haiku-20240307
68
- request_timeout: 30
69
-
70
- production:
71
- ruby_llm:
72
- request_timeout: 180
73
- max_retries: 5
74
- ```
75
-
76
- ### Environment Variables
77
-
78
- Environment variables use the `ROBOT_LAB_` prefix with double underscores for nested keys:
79
-
80
- ```bash
81
- ROBOT_LAB_RUBY_LLM__ANTHROPIC_API_KEY=sk-ant-...
82
- ROBOT_LAB_RUBY_LLM__MODEL=claude-sonnet-4
83
- ROBOT_LAB_RUBY_LLM__REQUEST_TIMEOUT=180
84
- ```
85
-
86
- RobotLab also falls back to standard provider environment variables (e.g. `ANTHROPIC_API_KEY`, `OPENAI_API_KEY`) when the prefixed versions are not set.
87
-
88
- ### Initializer (Logger Only)
89
-
90
- The only runtime-writable config attribute is the logger. The generated initializer sets it to the Rails logger:
91
-
92
- ```ruby title="config/initializers/robot_lab.rb"
93
- # frozen_string_literal: true
94
-
95
- # Set the RobotLab logger to use Rails.logger
96
- RobotLab.config.logger = Rails.logger
97
- ```
98
-
99
- ### Accessing Configuration
100
-
101
- ```ruby
102
- # Read configuration values
103
- RobotLab.config.ruby_llm.model #=> "claude-sonnet-4"
104
- RobotLab.config.ruby_llm.anthropic_api_key #=> "sk-ant-..."
105
- RobotLab.config.ruby_llm.request_timeout #=> 120
106
- RobotLab.config.streaming_enabled #=> true
107
- ```
108
-
109
- ## Creating Robots
110
-
111
- ### Robot Generator
112
-
113
- ```bash
114
- rails generate robot_lab:robot Support
115
- rails generate robot_lab:robot Billing --description="Handles billing inquiries"
116
- rails generate robot_lab:robot Router --routing
117
- ```
118
-
119
- ### Robot Class
120
-
121
- Robots are plain Ruby classes with a `.build` factory method that calls `RobotLab.build` with keyword arguments:
122
-
123
- ```ruby title="app/robots/support_robot.rb"
124
- # frozen_string_literal: true
125
-
126
- class SupportRobot
127
- def self.build(**options)
128
- RobotLab.build(
129
- name: "support",
130
- description: "Handles customer support inquiries",
131
- system_prompt: "You are a helpful support assistant.",
132
- model: "claude-sonnet-4",
133
- local_tools: [OrderLookup],
134
- **options
135
- )
136
- end
137
- end
138
- ```
139
-
140
- ### Routing Robot Class
141
-
142
- A routing robot classifies requests and activates optional tasks in a Network. It subclasses `RobotLab::Robot` and overrides `call(result)`:
143
-
144
- ```ruby title="app/robots/classifier_robot.rb"
145
- # frozen_string_literal: true
146
-
147
- class ClassifierRobot < RobotLab::Robot
148
- SYSTEM_PROMPT = <<~PROMPT
149
- You are a routing robot that classifies user requests.
150
-
151
- Analyze the user's request and respond with ONLY the category name.
152
- Valid categories: billing, technical, general
153
- PROMPT
154
-
155
- def self.build(**options)
156
- new(
157
- name: "classifier",
158
- description: "Classifies support requests",
159
- system_prompt: SYSTEM_PROMPT,
160
- **options
161
- )
162
- end
163
-
164
- def call(result)
165
- context = extract_run_context(result)
166
- message = context.delete(:message)
167
-
168
- robot_result = run(message, **context)
169
-
170
- new_result = result
171
- .with_context(@name.to_sym, robot_result)
172
- .continue(robot_result)
173
-
174
- category = robot_result.last_text_content.to_s.strip.downcase
175
-
176
- case category
177
- when /billing/ then new_result.activate(:billing)
178
- when /technical/ then new_result.activate(:technical)
179
- else new_result.activate(:general)
180
- end
181
- end
182
- end
183
- ```
184
-
185
- Use the routing robot as the first task in a network:
186
-
187
- ```ruby
188
- classifier = ClassifierRobot.build
189
- billing = BillingRobot.build
190
- technical = TechnicalRobot.build
191
-
192
- network = RobotLab.create_network(name: "support") do
193
- task :classifier, classifier, depends_on: :none
194
- task :billing, billing, depends_on: :optional
195
- task :technical, technical, depends_on: :optional
196
- end
197
-
198
- result = network.run(message: "I was charged twice")
199
- ```
200
-
201
- ### Custom Tool
202
-
203
- Tools subclass `RobotLab::Tool` (which extends `RubyLLM::Tool`):
204
-
205
- ```ruby title="app/tools/order_lookup.rb"
206
- # frozen_string_literal: true
207
-
208
- class OrderLookup < RobotLab::Tool
209
- description "Look up an order by ID"
210
- param :order_id, type: "string", desc: "The order ID to look up"
211
-
212
- def execute(order_id:)
213
- order = Order.find_by(id: order_id)
214
- return "Order not found" unless order
215
-
216
- {
217
- id: order.id,
218
- status: order.status,
219
- total: order.total.to_s,
220
- created_at: order.created_at.iso8601
221
- }.to_json
222
- end
223
- end
224
- ```
225
-
226
- ### Using in Controllers
227
-
228
- ```ruby title="app/controllers/chat_controller.rb"
229
- class ChatController < ApplicationController
230
- def create
231
- robot = SupportRobot.build
232
- result = robot.run(params[:message])
233
-
234
- render json: {
235
- response: result.last_text_content,
236
- robot_name: result.robot_name
237
- }
238
- end
239
- end
240
- ```
241
-
242
- ### Using a Network in Controllers
243
-
244
- Networks use `RobotLab.create_network` with a block DSL that defines tasks. Each task wraps a robot with dependency declarations:
245
-
246
- ```ruby title="app/controllers/chat_controller.rb"
247
- class ChatController < ApplicationController
248
- def create
249
- support_robot = SupportRobot.build
250
- billing_robot = BillingRobot.build
251
-
252
- network = RobotLab.create_network(name: "customer_service") do
253
- task :support, support_robot, depends_on: :none
254
- task :billing, billing_robot, depends_on: :optional
255
- end
256
-
257
- result = network.run(message: params[:message], user_id: current_user.id)
258
-
259
- # result is a SimpleFlow::Result
260
- # result.value is a RobotResult from the last robot
261
- render json: {
262
- response: result.value.last_text_content,
263
- robot_name: result.value.robot_name
264
- }
265
- end
266
- end
267
- ```
268
-
269
- ## Prompt Templates
270
-
271
- ### Template Location
272
-
273
- Templates are `.md` files with YAML front matter, stored in `app/prompts/` (auto-configured for Rails):
274
-
275
- ```
276
- app/prompts/
277
- ├── support.md
278
- ├── billing.md
279
- └── router.md
280
- ```
281
-
282
- ### Template Format
283
-
284
- ```markdown title="app/prompts/support.md"
285
- ---
286
- description: Customer support assistant
287
- parameters:
288
- company_name: null
289
- tone: friendly
290
- model: claude-sonnet-4
291
- temperature: 0.7
292
- ---
293
- You are a support agent for <%= company_name %>.
294
- Respond in a <%= tone %> manner.
295
-
296
- Your responsibilities:
297
- - Answer product questions
298
- - Help with order issues
299
- - Provide friendly assistance
300
- ```
301
-
302
- ### Template Usage
303
-
304
- ```ruby
305
- # Pass context to fill template parameters
306
- robot = RobotLab.build(
307
- name: "support",
308
- template: :support,
309
- context: { company_name: "Acme Corp" }
310
- )
311
-
312
- # Parameters with defaults (like `tone: friendly`) are optional.
313
- # Parameters set to null are required and must be provided via context.
314
- result = robot.run("I need help with my order")
315
- ```
316
-
317
- ## Action Cable Integration
318
-
319
- ### Channel
320
-
321
- ```ruby title="app/channels/chat_channel.rb"
322
- class ChatChannel < ApplicationCable::Channel
323
- def subscribed
324
- stream_from "chat_#{params[:session_id]}"
325
- end
326
-
327
- def receive(data)
328
- message = data["message"]
329
- session_id = data["session_id"]
330
-
331
- robot = SupportRobot.build
332
- result = robot.run(message)
333
-
334
- ActionCable.server.broadcast(
335
- "chat_#{session_id}",
336
- {
337
- event: "complete",
338
- response: result.last_text_content,
339
- robot_name: result.robot_name
340
- }
341
- )
342
- end
343
- end
344
- ```
345
-
346
- ### JavaScript Client
347
-
348
- ```javascript
349
- const channel = consumer.subscriptions.create(
350
- { channel: "ChatChannel", session_id: sessionId },
351
- {
352
- received(data) {
353
- if (data.event === "complete") {
354
- displayMessage(data.response);
355
- }
356
- }
357
- }
358
- );
359
-
360
- channel.send({ message: "Hello!", session_id: sessionId });
361
- ```
362
-
363
- ## Background Jobs
364
-
365
- ### RobotRunJob (Generated)
366
-
367
- The install generator creates `app/jobs/robot_run_job.rb` — an ActiveJob class that wraps robot execution with result persistence and optional Turbo Stream broadcasting.
368
-
369
- ```ruby
370
- # Enqueue from a controller
371
- RobotRunJob.perform_later(
372
- robot_class: "SupportRobot",
373
- message: params[:message],
374
- thread_id: session_id
375
- )
376
-
377
- render json: { status: "processing" }
378
- ```
379
-
380
- The job:
381
-
382
- 1. Finds or creates a `RobotLabThread` by `thread_id`
383
- 2. Resolves the robot class via `constantize.build`
384
- 3. Wires Turbo Stream callbacks when `turbo-rails` is available (graceful no-op otherwise)
385
- 4. Runs the robot and persists the result to `RobotLabResult`
386
- 5. Broadcasts completion or error events via Turbo Streams
387
-
388
- Customize the generated job to change queue name, retry policy, or error handling.
389
-
390
- ### Turbo Stream Token Streaming
391
-
392
- When `turbo-rails` is installed, `RobotRunJob` automatically streams content tokens and tool call badges to the browser in real time.
393
-
394
- #### View Setup
395
-
396
- Subscribe to the thread's Turbo Stream channel in your view:
397
-
398
- ```erb
399
- <%%= turbo_stream_from "robot_lab_thread_#{@thread_id}" %>
400
-
401
- <div id="robot_response"></div>
402
- <div id="robot_tools"></div>
403
- <div id="robot_status">Processing...</div>
404
- <div id="robot_errors"></div>
405
- ```
406
-
407
- As the robot generates tokens, they are appended to `#robot_response`. Tool calls appear as badges in `#robot_tools`. On completion, `#robot_status` is replaced with "Complete".
408
-
409
- #### TurboStreamCallbacks API
410
-
411
- `RobotLab::RailsIntegration::TurboStreamCallbacks` is a stateless utility module for building callback Procs. Use it outside of `RobotRunJob` for custom streaming setups:
412
-
413
- ```ruby
414
- # Check if Turbo Streams is available
415
- RobotLab::RailsIntegration::TurboStreamCallbacks.available?
416
-
417
- # Build a content streaming callback
418
- on_content = RobotLab::RailsIntegration::TurboStreamCallbacks.build_content_callback(
419
- stream_name: "robot_lab_thread_#{thread_id}",
420
- target: "robot_response" # default
421
- )
422
-
423
- # Build a tool call badge callback
424
- on_tool_call = RobotLab::RailsIntegration::TurboStreamCallbacks.build_tool_call_callback(
425
- stream_name: "robot_lab_thread_#{thread_id}",
426
- target: "robot_tools" # default
427
- )
428
-
429
- # Wire into a robot at build time
430
- robot = SupportRobot.build(on_content: on_content, on_tool_call: on_tool_call)
431
- robot.run(message)
432
- ```
433
-
434
- The stream name convention is `"robot_lab_thread_#{thread_id}"`, matching the `RobotLabThread.session_id` pattern.
435
-
436
- ### Custom Background Job
437
-
438
- For full control, write your own job instead of using the generated one:
439
-
440
- ```ruby title="app/jobs/process_message_job.rb"
441
- class ProcessMessageJob < ApplicationJob
442
- queue_as :default
443
-
444
- def perform(session_id:, message:, user_id:)
445
- robot = SupportRobot.build
446
- result = robot.run(message)
447
-
448
- ActionCable.server.broadcast(
449
- "chat_#{session_id}",
450
- {
451
- event: "complete",
452
- response: result.last_text_content,
453
- robot_name: result.robot_name
454
- }
455
- )
456
- end
457
- end
458
- ```
459
-
460
- ## Testing
461
-
462
- ### Test Configuration
463
-
464
- Use `config/robot_lab.yml` to configure the test environment with a faster, cheaper model:
465
-
466
- ```yaml title="config/robot_lab.yml"
467
- test:
468
- max_iterations: 3
469
- streaming_enabled: false
470
- ruby_llm:
471
- model: claude-3-haiku-20240307
472
- request_timeout: 30
473
- max_retries: 1
474
- ```
475
-
476
- ### Robot Tests
477
-
478
- ```ruby title="test/robots/support_robot_test.rb"
479
- require "test_helper"
480
-
481
- class SupportRobotTest < ActiveSupport::TestCase
482
- test "builds valid robot" do
483
- robot = SupportRobot.build
484
- assert_equal "support", robot.name
485
- end
486
-
487
- test "robot has correct model" do
488
- robot = SupportRobot.build
489
- assert_equal "claude-sonnet-4", robot.model
490
- end
491
-
492
- test "robot has local tools" do
493
- robot = SupportRobot.build
494
- tool_names = robot.local_tools.map(&:name)
495
- assert_includes tool_names, "order_lookup"
496
- end
497
- end
498
- ```
499
-
500
- ### Integration Tests
501
-
502
- ```ruby title="test/integration/chat_test.rb"
503
- require "test_helper"
504
-
505
- class ChatTest < ActionDispatch::IntegrationTest
506
- test "processes chat message" do
507
- VCR.use_cassette("chat_response") do
508
- post chat_path, params: { message: "Hello" }
509
- assert_response :success
510
-
511
- json = JSON.parse(response.body)
512
- assert json["response"].present?
513
- end
514
- end
515
- end
516
- ```
517
-
518
- ## Models
519
-
520
- ### Thread Model
521
-
522
- ```ruby title="app/models/robot_lab_thread.rb"
523
- class RobotLabThread < ApplicationRecord
524
- has_many :results,
525
- class_name: "RobotLabResult",
526
- foreign_key: :session_id,
527
- primary_key: :session_id,
528
- dependent: :destroy
529
-
530
- validates :session_id, presence: true, uniqueness: true
531
-
532
- def self.find_or_create_by_session_id(id)
533
- find_or_create_by(session_id: id)
534
- end
535
-
536
- def last_result
537
- results.order(sequence_number: :desc).first
538
- end
539
- end
540
- ```
541
-
542
- ### Result Model
543
-
544
- ```ruby title="app/models/robot_lab_result.rb"
545
- class RobotLabResult < ApplicationRecord
546
- belongs_to :thread,
547
- class_name: "RobotLabThread",
548
- foreign_key: :session_id,
549
- primary_key: :session_id
550
-
551
- validates :session_id, presence: true
552
- validates :robot_name, presence: true
553
-
554
- default_scope { order(sequence_number: :asc) }
555
-
556
- def to_robot_result
557
- RobotLab::RobotResult.new(
558
- robot_name: robot_name,
559
- output: (output_data || []).map { |d| RobotLab::Message.from_hash(d.symbolize_keys) },
560
- tool_calls: (tool_calls_data || []).map { |d| RobotLab::Message.from_hash(d.symbolize_keys) },
561
- stop_reason: stop_reason
562
- )
563
- end
564
- end
565
- ```
566
-
567
- ## Best Practices
568
-
569
- ### 1. Use Service Objects
570
-
571
- ```ruby title="app/services/chat_service.rb"
572
- class ChatService
573
- def initialize(user:)
574
- @user = user
575
- end
576
-
577
- def process(message)
578
- robot = SupportRobot.build
579
- result = robot.run(message)
580
-
581
- {
582
- response: result.last_text_content,
583
- robot_name: result.robot_name
584
- }
585
- end
586
-
587
- def process_with_network(message)
588
- support_robot = SupportRobot.build
589
- billing_robot = BillingRobot.build
590
-
591
- network = RobotLab.create_network(name: "customer_service") do
592
- task :support, support_robot, depends_on: :none
593
- task :billing, billing_robot, depends_on: :optional
594
- end
595
-
596
- result = network.run(message: message, user_id: @user.id)
597
-
598
- {
599
- response: result.value.last_text_content,
600
- robot_name: result.value.robot_name
601
- }
602
- end
603
- end
604
- ```
605
-
606
- ### 2. Handle Errors
607
-
608
- ```ruby
609
- def create
610
- result = ChatService.new(user: current_user).process(params[:message])
611
- render json: result
612
- rescue RobotLab::Error => e
613
- render json: { error: e.message }, status: :unprocessable_entity
614
- rescue StandardError => e
615
- Rails.logger.error("Chat error: #{e.message}")
616
- render json: { error: "An error occurred" }, status: :internal_server_error
617
- end
618
- ```
619
-
620
- ### 3. Rate Limiting
621
-
622
- ```ruby
623
- class ChatController < ApplicationController
624
- before_action :check_rate_limit
625
-
626
- private
627
-
628
- def check_rate_limit
629
- key = "chat_rate:#{current_user.id}"
630
- count = Rails.cache.increment(key, 1, expires_in: 1.minute)
631
-
632
- if count > 10
633
- render json: { error: "Rate limit exceeded" }, status: :too_many_requests
634
- end
635
- end
636
- end
637
- ```
638
-
639
- ## Next Steps
640
-
641
- - [Building Robots](building-robots.md) - Robot patterns
642
- - [Creating Networks](creating-networks.md) - Network configuration