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