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,476 +0,0 @@
1
- # Multi-Robot Network
2
-
3
- Customer service system with intelligent routing using SimpleFlow pipelines.
4
-
5
- ## Overview
6
-
7
- This example demonstrates a multi-robot network where a classifier routes customer inquiries to specialized support robots using SimpleFlow's optional task activation.
8
-
9
- The runnable version is
10
- [`examples/03_network.rb`](https://github.com/MadBomber/robot_lab/blob/main/examples/03_network.rb);
11
- the snippets here expand it with a fourth specialist and per-task configuration.
12
-
13
- > [!IMPORTANT]
14
- > Two names are in play and they are easy to confuse.
15
- > `result.activate(:x)` takes a **task** name (the first argument to `task`), while
16
- > `result.with_context(@name.to_sym, ...)` keys the context by the **robot's** name.
17
- > Activating a task name that was never declared **raises `ArgumentError` and aborts
18
- > the run** — it is not a silent no-op. Give each robot the same name as its task and
19
- > both problems disappear.
20
-
21
- ## Complete Example
22
-
23
- ```ruby
24
- #!/usr/bin/env ruby
25
-
26
- require "bundler/setup"
27
- require "robot_lab"
28
-
29
- # Custom classifier that routes to specialists
30
- class ClassifierRobot < RobotLab::Robot
31
- def call(result)
32
- context = extract_run_context(result)
33
- message = context.delete(:message)
34
- robot_result = run(message, **context)
35
-
36
- new_result = result
37
- .with_context(@name.to_sym, robot_result)
38
- .continue(robot_result)
39
-
40
- # Route based on classification
41
- category = robot_result.last_text_content.to_s.strip.downcase
42
-
43
- case category
44
- when /billing/ then new_result.activate(:billing_agent)
45
- when /technical/ then new_result.activate(:tech_agent)
46
- when /account/ then new_result.activate(:account_agent)
47
- else new_result.activate(:general_agent)
48
- end
49
- end
50
- end
51
-
52
- # Classifier robot
53
- classifier = ClassifierRobot.new(
54
- name: "classifier",
55
- description: "Classifies customer inquiries",
56
- system_prompt: <<~PROMPT,
57
- You are a customer inquiry classifier. Analyze the customer's message
58
- and respond with exactly ONE of these categories:
59
-
60
- - BILLING (payment issues, invoices, refunds, subscriptions)
61
- - TECHNICAL (bugs, errors, how-to questions, feature requests)
62
- - ACCOUNT (login issues, profile changes, security concerns)
63
- - GENERAL (everything else)
64
-
65
- Respond with ONLY the category name, nothing else.
66
- PROMPT
67
- model: "claude-sonnet-4"
68
- )
69
-
70
- # Billing specialist
71
- billing_agent = RobotLab.build(
72
- name: "billing_agent",
73
- description: "Handles billing inquiries",
74
- system_prompt: <<~PROMPT,
75
- You are a billing support specialist. You help customers with:
76
- - Payment issues and refunds
77
- - Invoice questions
78
- - Subscription management
79
- - Pricing inquiries
80
-
81
- Be helpful, empathetic, and provide clear next steps.
82
- PROMPT
83
- model: "claude-sonnet-4"
84
- )
85
-
86
- # Technical support
87
- tech_agent = RobotLab.build(
88
- name: "tech_agent",
89
- description: "Handles technical issues",
90
- system_prompt: <<~PROMPT,
91
- You are a technical support specialist. You help customers with:
92
- - Bug reports and troubleshooting
93
- - Feature explanations
94
- - Integration questions
95
- - Best practices
96
-
97
- Ask clarifying questions when needed. Provide step-by-step solutions.
98
- PROMPT
99
- model: "claude-sonnet-4"
100
- )
101
-
102
- # Account specialist
103
- account_agent = RobotLab.build(
104
- name: "account_agent",
105
- description: "Handles account issues",
106
- system_prompt: <<~PROMPT,
107
- You are an account support specialist. You help customers with:
108
- - Login and authentication issues
109
- - Profile and settings changes
110
- - Security concerns
111
- - Account recovery
112
-
113
- Prioritize security while being helpful.
114
- PROMPT
115
- model: "claude-sonnet-4"
116
- )
117
-
118
- # General support
119
- general_agent = RobotLab.build(
120
- name: "general_agent",
121
- description: "Handles general inquiries",
122
- system_prompt: <<~PROMPT,
123
- You are a general support agent. You help customers with:
124
- - Product information
125
- - General questions
126
- - Feedback collection
127
- - Routing to appropriate departments
128
-
129
- Be friendly and informative.
130
- PROMPT
131
- model: "claude-sonnet-4"
132
- )
133
-
134
- # Create the network with optional task routing
135
- network = RobotLab.create_network(name: "customer_service") do
136
- task :classifier, classifier, depends_on: :none
137
- task :billing_agent, billing_agent, depends_on: :optional
138
- task :tech_agent, tech_agent, depends_on: :optional
139
- task :account_agent, account_agent, depends_on: :optional
140
- task :general_agent, general_agent, depends_on: :optional
141
- end
142
-
143
- # Run the support system
144
- puts "Customer Service System"
145
- puts "=" * 50
146
- puts
147
-
148
- test_inquiries = [
149
- "I was charged twice for my subscription last month",
150
- "How do I reset my password?",
151
- "The app crashes when I try to upload photos",
152
- "What features are included in the pro plan?"
153
- ]
154
-
155
- test_inquiries.each do |inquiry|
156
- puts "Customer: #{inquiry}"
157
- puts "-" * 50
158
-
159
- result = network.run(message: inquiry)
160
-
161
- # Show classification
162
- if result.context[:classifier]
163
- puts "Classification: #{result.context[:classifier].last_text_content}"
164
- end
165
-
166
- # Show specialist response
167
- if result.value.is_a?(RobotLab::RobotResult)
168
- puts "Handled by: #{result.value.robot_name}"
169
- puts "Response: #{result.value.last_text_content[0..200]}..."
170
- end
171
-
172
- puts
173
- puts "=" * 50
174
- puts
175
- end
176
- ```
177
-
178
- ## ClassifierRobot Pattern
179
-
180
- The key to conditional routing is overriding the `call` method. The base `Robot#call` extracts the message from the `SimpleFlow::Result` and calls `run`. A classifier overrides this to inspect the output and activate the appropriate optional task:
181
-
182
- ```ruby
183
- class ClassifierRobot < RobotLab::Robot
184
- def call(result)
185
- # 1. Extract run context from the SimpleFlow result
186
- context = extract_run_context(result)
187
-
188
- # 2. Pull out the message (it's a key in the context hash)
189
- message = context.delete(:message)
190
-
191
- # 3. Run the robot to get a classification
192
- robot_result = run(message, **context)
193
-
194
- # 4. Store our result and continue the pipeline
195
- new_result = result
196
- .with_context(@name.to_sym, robot_result)
197
- .continue(robot_result)
198
-
199
- # 5. Activate the appropriate optional task based on output.
200
- # These symbols MUST match the task names declared in create_network.
201
- category = robot_result.last_text_content.to_s.strip.downcase
202
- case category
203
- when /billing/ then new_result.activate(:billing_agent)
204
- when /technical/ then new_result.activate(:tech_agent)
205
- else new_result.activate(:general_agent)
206
- end
207
- end
208
- end
209
- ```
210
-
211
- > [!WARNING]
212
- > `activate` validates its argument and **raises**. Activating `:billing` when the
213
- > network declared `task :billing_agent, ...` aborts the run with
214
- > `ArgumentError: Step :classifier attempted to activate unknown step :billing`.
215
- > Activating a task that was *not* declared `depends_on: :optional` raises too:
216
- > `ArgumentError: Step :classifier attempted to activate non-optional step :billing_agent.`
217
- > Use `result.activated_steps` to inspect what routing actually did.
218
-
219
- !!! note "extract_run_context"
220
- The `extract_run_context(result)` method is a protected helper on `Robot`. It extracts `run_params` from the SimpleFlow result context, handles value propagation from previous steps, and separates robot-specific params (`mcp:`, `tools:`, `memory:`, `network_memory:`) from the message and other context.
221
-
222
- ## With Context Passing
223
-
224
- Enhanced version where the classifier passes additional context to specialists:
225
-
226
- ```ruby
227
- class ContextAwareClassifier < RobotLab::Robot
228
- def call(result)
229
- context = extract_run_context(result)
230
- message = context.delete(:message)
231
- robot_result = run(message, **context)
232
-
233
- # Store classification and original message in context for specialist
234
- new_result = result
235
- .with_context(@name.to_sym, robot_result)
236
- .with_context(:classification, robot_result.last_text_content.strip)
237
- .with_context(:original_message, result.context[:run_params][:message])
238
- .continue(robot_result)
239
-
240
- category = robot_result.last_text_content.to_s.downcase
241
- case category
242
- when /billing/ then new_result.activate(:billing_agent)
243
- when /technical/ then new_result.activate(:tech_agent)
244
- else new_result.activate(:general_agent)
245
- end
246
- end
247
- end
248
-
249
- # Specialist can access shared context
250
- class BillingAgent < RobotLab::Robot
251
- def call(result)
252
- # Access context from classifier
253
- classification = result.context[:classification]
254
- original_message = result.context[:original_message]
255
-
256
- context = extract_run_context(result)
257
- message = context.delete(:message)
258
-
259
- robot_result = run(message, **context)
260
-
261
- result.with_context(@name.to_sym, robot_result).continue(robot_result)
262
- end
263
- end
264
- ```
265
-
266
- ## Per-Task Configuration
267
-
268
- Tasks can carry their own context, a tool allowlist, and MCP servers:
269
-
270
- ```ruby
271
- # Tools must already be ATTACHED to the robot...
272
- billing_agent = RobotLab.build(
273
- name: "billing_agent",
274
- system_prompt: "You handle billing.",
275
- local_tools: [RefundTool, InvoiceTool, AuditTool]
276
- )
277
-
278
- network = RobotLab.create_network(name: "support") do
279
- task :classifier, classifier, depends_on: :none
280
- task :billing_agent, billing_agent,
281
- context: { department: "billing", escalation_level: 2 },
282
- tools: [RefundTool, InvoiceTool], # ...and this SELECTS from them
283
- depends_on: :optional
284
- task :tech_agent, tech_agent,
285
- context: { department: "technical" },
286
- mcp: [filesystem_server],
287
- tools: :inherit, # send every discovered MCP tool
288
- depends_on: :optional
289
- end
290
- ```
291
-
292
- > [!WARNING]
293
- > Per-task `tools:` is an **allowlist over tools the robot already has** — it never
294
- > attaches anything. Give a task `tools: [RefundTool]` for a robot whose
295
- > `local_tools` are empty and it sends zero tools.
296
- >
297
- > Matching is by string comparison against each attached tool's `name`, so the two
298
- > sides must agree. `local_tools: [RefundTool]` (the class) matches
299
- > `tools: [RefundTool]`, but `local_tools: [RefundTool.new]` (an instance, whose
300
- > name is `"refund"`) does not — use `tools: %w[refund]` there.
301
- >
302
- > Omitting `tools:` from a task means `:none`: the robot sends no tools at all.
303
- > Use `tools: :inherit` for "send everything attached".
304
-
305
- The `Task` wrapper deep-merges per-task context with the network's run params before delegating to the robot's `call` method.
306
-
307
- ## Pipeline Pattern
308
-
309
- Sequential processing pipeline where each robot depends on the previous:
310
-
311
- ```ruby
312
- extractor = RobotLab.build(
313
- name: "extractor",
314
- system_prompt: "Extract key information from documents."
315
- )
316
-
317
- analyzer = RobotLab.build(
318
- name: "analyzer",
319
- system_prompt: "Analyze extracted data and provide insights."
320
- )
321
-
322
- formatter = RobotLab.build(
323
- name: "formatter",
324
- system_prompt: "Format analysis results into a clear report."
325
- )
326
-
327
- network = RobotLab.create_network(name: "document_processor") do
328
- task :extract, extractor, depends_on: :none
329
- task :analyze, analyzer, depends_on: [:extract]
330
- task :format, formatter, depends_on: [:analyze]
331
- end
332
-
333
- result = network.run(message: "Process this document")
334
- puts result.value.last_text_content
335
- ```
336
-
337
- ## Parallel Analysis Pattern
338
-
339
- Fan-out / fan-in pattern where multiple robots analyze in parallel and a synthesizer merges results:
340
-
341
- ```ruby
342
- # Robot names deliberately match their task names -- result.context is keyed
343
- # by the ROBOT's name, so mismatched names produce nil lookups below.
344
- sentiment = RobotLab.build(name: "sentiment", system_prompt: "Score sentiment.")
345
- entities = RobotLab.build(name: "entities", system_prompt: "Extract entities.")
346
- keywords = RobotLab.build(name: "keywords", system_prompt: "Extract keywords.")
347
-
348
- network = RobotLab.create_network(name: "multi_analysis") do
349
- task :prepare, preparer, depends_on: :none
350
-
351
- # These run in parallel (all depend on :prepare)
352
- task :sentiment, sentiment, depends_on: [:prepare]
353
- task :entities, entities, depends_on: [:prepare]
354
- task :keywords, keywords, depends_on: [:prepare]
355
-
356
- # Waits for all three to complete
357
- task :summarize, summarizer, depends_on: [:sentiment, :entities, :keywords]
358
- end
359
-
360
- result = network.run(message: "Analyze this text")
361
-
362
- # Access parallel results from context (keyed by robot name)
363
- puts "Sentiment: #{result.context[:sentiment].last_text_content}"
364
- puts "Entities: #{result.context[:entities].last_text_content}"
365
- puts "Keywords: #{result.context[:keywords].last_text_content}"
366
- puts "Summary: #{result.value.last_text_content}"
367
- ```
368
-
369
- > [!WARNING]
370
- > `result.context` is keyed by `@name` of the robot that wrote it, not by the task
371
- > name. Had these robots kept names like `sentiment_analyzer`, the lookups above
372
- > would return `nil` even though every task ran. Keep robot name and task name
373
- > identical.
374
-
375
- ## Shared Memory in Networks
376
-
377
- Networks provide a shared `Memory` instance that all robots can read and write. This is especially useful for parallel robots that need to coordinate:
378
-
379
- ```ruby
380
- class AnalysisRobot < RobotLab::Robot
381
- def initialize(memory_key:, **opts)
382
- super(**opts)
383
- @memory_key = memory_key
384
- end
385
-
386
- def call(result)
387
- context = extract_run_context(result)
388
- network_memory = context.delete(:network_memory)
389
- message = context.delete(:message)
390
-
391
- robot_result = run(message, network_memory: network_memory, **context)
392
-
393
- # Write parsed results to shared memory
394
- if network_memory
395
- network_memory.current_writer = @name
396
- network_memory.set(@memory_key, robot_result.last_text_content)
397
- end
398
-
399
- result.with_context(@name.to_sym, robot_result).continue(robot_result)
400
- end
401
- end
402
- ```
403
-
404
- ## Conditional Halting
405
-
406
- Use `result.halt` to stop the pipeline early:
407
-
408
- ```ruby
409
- class ValidatorRobot < RobotLab::Robot
410
- def call(result)
411
- context = extract_run_context(result)
412
- message = context.delete(:message)
413
- robot_result = run(message, **context)
414
-
415
- if robot_result.last_text_content.include?("INVALID")
416
- # Halt the pipeline early
417
- result.halt(robot_result)
418
- else
419
- result.with_context(@name.to_sym, robot_result).continue(robot_result)
420
- end
421
- end
422
- end
423
-
424
- network = RobotLab.create_network(name: "validated_pipeline") do
425
- task :validate, validator, depends_on: :none
426
- task :process, processor, depends_on: [:validate] # Only runs if not halted
427
- end
428
-
429
- result = network.run(message: "Process this")
430
- if result.continue?
431
- puts "Processing complete: #{result.value.last_text_content}"
432
- else
433
- puts "Validation failed: #{result.value.last_text_content}"
434
- end
435
- ```
436
-
437
- > [!WARNING]
438
- > `SimpleFlow::Result` has **no** `halted?` or `continued?` predicate — calling
439
- > either raises `NoMethodError`. The predicate is `continue?`, which returns
440
- > `false` after `halt`. The full public API is exactly `activate`,
441
- > `activated_steps`, `context`, `continue`, `continue?`, `errors`, `halt`, `value`,
442
- > `with_context`, and `with_error`. `with_value` is **private** — calling it raises
443
- > `NoMethodError`; use `continue(value)` or `halt(value)` to set the value.
444
-
445
- ## Running
446
-
447
- ```bash
448
- export ANTHROPIC_API_KEY="your-key"
449
-
450
- # Classifier + three specialists with optional-task routing
451
- ruby examples/03_network.rb
452
-
453
- # Shared reactive memory across concurrent robots
454
- ruby examples/07_network_memory.rb
455
-
456
- # Network visualization and introspection (no LLM calls)
457
- ruby examples/11_network_introspection.rb
458
- ```
459
-
460
- ## Key Concepts
461
-
462
- 1. **SimpleFlow Pipeline**: DAG-based execution with dependency management via `depends_on:`. The DSL method is `task` — there is no `step`, no `router:` kwarg, and no `Router` class
463
- 2. **Optional Tasks**: Use `depends_on: :optional` for tasks activated dynamically by classifiers
464
- 3. **Robot#call Override**: Routing is written in Ruby — subclass `Robot`, override `call`, and call `result.activate(:task_name)`
465
- 4. **extract_run_context**: Helper method to extract message and params from `SimpleFlow::Result`
466
- 5. **Context Flow**: `result.context` is keyed by the **robot's** name (`with_context(@name.to_sym, ...)`), while `activate` takes a **task** name — keep the two identical
467
- 6. **Result predicates**: `continue?` (not `halted?`), plus `activated_steps` to see what routing actually did
468
- 7. **Parallel Execution**: Tasks with the same dependencies run concurrently; cap in-flight LLM calls with `RunConfig.new(max_concurrent_robots: N)`
469
- 8. **Shared Memory**: Network memory (`network_memory:`) enables inter-robot communication
470
- 9. **Per-Task Configuration**: Each task can carry its own context, MCP servers, and a `tools:` allowlist over the robot's already-attached tools
471
-
472
- ## See Also
473
-
474
- - [Creating Networks Guide](../guides/creating-networks.md)
475
- - [Network Orchestration](../architecture/network-orchestration.md)
476
- - [API Reference: Network](../api/core/network.md)