robot_lab 0.3.0 → 0.5.0

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 (274) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +60 -0
  3. data/README.md +29 -19
  4. data/lib/robot_lab/errors.rb +4 -0
  5. data/lib/robot_lab/hook_context.rb +9 -2
  6. data/lib/robot_lab/mcp/registry.rb +99 -0
  7. data/lib/robot_lab/mcp/server.rb +47 -83
  8. data/lib/robot_lab/mcp/server_discovery.rb +22 -9
  9. data/lib/robot_lab/mcp.rb +259 -0
  10. data/lib/robot_lab/robot/mcp_management.rb +159 -105
  11. data/lib/robot_lab/robot/tool_call_gating.rb +144 -0
  12. data/lib/robot_lab/robot.rb +72 -99
  13. data/lib/robot_lab/run_config.rb +3 -2
  14. data/lib/robot_lab/tool.rb +8 -23
  15. data/lib/robot_lab/version.rb +1 -1
  16. data/lib/robot_lab.rb +1 -4
  17. metadata +16 -292
  18. data/.architecture/AGENTS.md +0 -32
  19. data/.architecture/config.yml +0 -8
  20. data/.architecture/members.yml +0 -60
  21. data/.architecture/reviews/feature-free-will.md +0 -490
  22. data/.architecture/reviews/overall-codebase.md +0 -427
  23. data/.claude/settings.local.json +0 -57
  24. data/.codex/config.toml +0 -2
  25. data/.envrc +0 -6
  26. data/.github/workflows/deploy-github-pages.yml +0 -52
  27. data/.irbrc +0 -6
  28. data/.loki +0 -65
  29. data/.quality/reek_baseline.txt +0 -43
  30. data/.rubocop.yml +0 -10
  31. data/Archspec.rb +0 -44
  32. data/CLAUDE.md +0 -141
  33. data/COMMITS.md +0 -196
  34. data/Rakefile +0 -193
  35. data/_typos.toml +0 -21
  36. data/agent2agent_review.md +0 -192
  37. data/agentf_improvements.md +0 -253
  38. data/agents.md +0 -14
  39. data/docs/api/core/index.md +0 -147
  40. data/docs/api/core/memory.md +0 -578
  41. data/docs/api/core/network.md +0 -553
  42. data/docs/api/core/result.md +0 -211
  43. data/docs/api/core/robot.md +0 -1667
  44. data/docs/api/core/state.md +0 -145
  45. data/docs/api/core/tool.md +0 -520
  46. data/docs/api/errors.md +0 -323
  47. data/docs/api/hooks.md +0 -469
  48. data/docs/api/index.md +0 -137
  49. data/docs/api/mcp/client.md +0 -387
  50. data/docs/api/mcp/index.md +0 -267
  51. data/docs/api/mcp/server.md +0 -252
  52. data/docs/api/mcp/transports.md +0 -339
  53. data/docs/api/messages/index.md +0 -126
  54. data/docs/api/messages/text-message.md +0 -170
  55. data/docs/api/messages/tool-call-message.md +0 -206
  56. data/docs/api/messages/tool-result-message.md +0 -242
  57. data/docs/api/messages/user-message.md +0 -232
  58. data/docs/api/skills.md +0 -423
  59. data/docs/api/streaming/context.md +0 -280
  60. data/docs/api/streaming/events.md +0 -185
  61. data/docs/api/streaming/index.md +0 -156
  62. data/docs/api/support.md +0 -846
  63. data/docs/architecture/core-concepts.md +0 -575
  64. data/docs/architecture/index.md +0 -252
  65. data/docs/architecture/message-flow.md +0 -366
  66. data/docs/architecture/network-orchestration.md +0 -478
  67. data/docs/architecture/robot-execution.md +0 -396
  68. data/docs/architecture/state-management.md +0 -410
  69. data/docs/assets/css/custom.css +0 -56
  70. data/docs/assets/images/robot_lab.jpg +0 -0
  71. data/docs/concepts.md +0 -641
  72. data/docs/examples/basic-chat.md +0 -258
  73. data/docs/examples/index.md +0 -425
  74. data/docs/examples/mcp-server.md +0 -403
  75. data/docs/examples/multi-robot-network.md +0 -476
  76. data/docs/examples/tool-usage.md +0 -468
  77. data/docs/getting-started/configuration.md +0 -645
  78. data/docs/getting-started/index.md +0 -56
  79. data/docs/getting-started/installation.md +0 -197
  80. data/docs/getting-started/quick-start.md +0 -293
  81. data/docs/guides/building-robots.md +0 -1187
  82. data/docs/guides/creating-networks.md +0 -736
  83. data/docs/guides/hooks.md +0 -1206
  84. data/docs/guides/index.md +0 -78
  85. data/docs/guides/knowledge.md +0 -219
  86. data/docs/guides/mcp-integration.md +0 -740
  87. data/docs/guides/memory.md +0 -458
  88. data/docs/guides/observability.md +0 -733
  89. data/docs/guides/streaming.md +0 -323
  90. data/docs/guides/using-tools.md +0 -780
  91. data/docs/index.md +0 -280
  92. data/docs/superpowers/plans/2026-05-06-agentskills.md +0 -1303
  93. data/docs/superpowers/specs/2026-05-06-agentskills-design.md +0 -247
  94. data/examples/.envrc +0 -3
  95. data/examples/01_simple_robot.rb +0 -31
  96. data/examples/02_tools.rb +0 -109
  97. data/examples/03_network.rb +0 -118
  98. data/examples/04_mcp.rb +0 -217
  99. data/examples/05_streaming.rb +0 -137
  100. data/examples/06_prompt_templates.rb +0 -325
  101. data/examples/07_network_memory.rb +0 -313
  102. data/examples/08_llm_config.rb +0 -162
  103. data/examples/09_chaining.rb +0 -251
  104. data/examples/10_memory.rb +0 -315
  105. data/examples/11_network_introspection.rb +0 -239
  106. data/examples/12_message_bus.rb +0 -67
  107. data/examples/13_spawn.rb +0 -85
  108. data/examples/14_rusty_circuit/.envrc +0 -1
  109. data/examples/14_rusty_circuit/.gitignore +0 -1
  110. data/examples/14_rusty_circuit/README.md +0 -1
  111. data/examples/14_rusty_circuit/comic.rb +0 -149
  112. data/examples/14_rusty_circuit/display.rb +0 -217
  113. data/examples/14_rusty_circuit/heckler.rb +0 -65
  114. data/examples/14_rusty_circuit/open_mic.rb +0 -132
  115. data/examples/14_rusty_circuit/prompts/open_mic_comic.md +0 -20
  116. data/examples/14_rusty_circuit/prompts/open_mic_heckler.md +0 -23
  117. data/examples/14_rusty_circuit/prompts/open_mic_scout.md +0 -20
  118. data/examples/14_rusty_circuit/scout.rb +0 -165
  119. data/examples/14_rusty_circuit/show.log +0 -234
  120. data/examples/15_memory_network_and_bus/.envrc +0 -1
  121. data/examples/15_memory_network_and_bus/README.md +0 -66
  122. data/examples/15_memory_network_and_bus/editor_in_chief.rb +0 -24
  123. data/examples/15_memory_network_and_bus/editorial_pipeline.rb +0 -213
  124. data/examples/15_memory_network_and_bus/linux_writer.rb +0 -80
  125. data/examples/15_memory_network_and_bus/os_editor.rb +0 -48
  126. data/examples/15_memory_network_and_bus/os_writer.rb +0 -49
  127. data/examples/15_memory_network_and_bus/output/combined_article.md +0 -11
  128. data/examples/15_memory_network_and_bus/output/final_article.md +0 -8
  129. data/examples/15_memory_network_and_bus/output/linux_draft.md +0 -7
  130. data/examples/15_memory_network_and_bus/output/mac_draft.md +0 -7
  131. data/examples/15_memory_network_and_bus/output/memory.json +0 -13
  132. data/examples/15_memory_network_and_bus/output/revision_1.md +0 -29
  133. data/examples/15_memory_network_and_bus/output/revision_2.md +0 -50
  134. data/examples/15_memory_network_and_bus/output/revision_3.md +0 -8
  135. data/examples/15_memory_network_and_bus/output/windows_draft.md +0 -7
  136. data/examples/15_memory_network_and_bus/prompts/os_advocate.md +0 -13
  137. data/examples/15_memory_network_and_bus/prompts/os_chief.md +0 -13
  138. data/examples/15_memory_network_and_bus/prompts/os_editor.md +0 -13
  139. data/examples/16_writers_room/.envrc +0 -1
  140. data/examples/16_writers_room/display.rb +0 -158
  141. data/examples/16_writers_room/output/.gitignore +0 -4
  142. data/examples/16_writers_room/output/README.md +0 -69
  143. data/examples/16_writers_room/output/opus_001.md +0 -263
  144. data/examples/16_writers_room/output/opus_001_notes.log +0 -470
  145. data/examples/16_writers_room/output/opus_002.md +0 -245
  146. data/examples/16_writers_room/output/opus_002_notes.log +0 -546
  147. data/examples/16_writers_room/output/opus_002_screenplay.md +0 -7989
  148. data/examples/16_writers_room/output/opus_002_screenplay_notes.md +0 -993
  149. data/examples/16_writers_room/prompts/screenplay_writer.md +0 -66
  150. data/examples/16_writers_room/prompts/writer.md +0 -37
  151. data/examples/16_writers_room/room.rb +0 -186
  152. data/examples/16_writers_room/tools.rb +0 -173
  153. data/examples/16_writers_room/writer.rb +0 -121
  154. data/examples/16_writers_room/writers_room.rb +0 -256
  155. data/examples/17_skills.rb +0 -234
  156. data/examples/18_rails/.envrc +0 -3
  157. data/examples/18_rails/.gitignore +0 -5
  158. data/examples/18_rails/Gemfile +0 -11
  159. data/examples/18_rails/README.md +0 -67
  160. data/examples/18_rails/Rakefile +0 -4
  161. data/examples/18_rails/app/controllers/application_controller.rb +0 -4
  162. data/examples/18_rails/app/controllers/chat_controller.rb +0 -50
  163. data/examples/18_rails/app/jobs/application_job.rb +0 -4
  164. data/examples/18_rails/app/jobs/robot_run_job.rb +0 -25
  165. data/examples/18_rails/app/models/application_record.rb +0 -5
  166. data/examples/18_rails/app/models/robot_lab_result.rb +0 -36
  167. data/examples/18_rails/app/models/robot_lab_thread.rb +0 -23
  168. data/examples/18_rails/app/robots/chat_robot.rb +0 -25
  169. data/examples/18_rails/app/tools/time_tool.rb +0 -9
  170. data/examples/18_rails/app/views/chat/_user_message.html.erb +0 -1
  171. data/examples/18_rails/app/views/chat/index.html.erb +0 -67
  172. data/examples/18_rails/app/views/layouts/application.html.erb +0 -49
  173. data/examples/18_rails/bin/dev +0 -7
  174. data/examples/18_rails/bin/rails +0 -6
  175. data/examples/18_rails/bin/setup +0 -15
  176. data/examples/18_rails/config/application.rb +0 -33
  177. data/examples/18_rails/config/cable.yml +0 -2
  178. data/examples/18_rails/config/database.yml +0 -5
  179. data/examples/18_rails/config/environment.rb +0 -4
  180. data/examples/18_rails/config/initializers/robot_lab.rb +0 -11
  181. data/examples/18_rails/config/routes.rb +0 -6
  182. data/examples/18_rails/config.ru +0 -4
  183. data/examples/18_rails/db/migrate/001_create_robot_lab_tables.rb +0 -32
  184. data/examples/19_token_tracking.rb +0 -138
  185. data/examples/20_circuit_breaker.rb +0 -147
  186. data/examples/21_learning_loop.rb +0 -181
  187. data/examples/22_context_compression.rb +0 -172
  188. data/examples/23_convergence.rb +0 -133
  189. data/examples/24_structured_delegation.rb +0 -151
  190. data/examples/25_history_search/conversation.jsonl +0 -30
  191. data/examples/25_history_search.rb +0 -133
  192. data/examples/26_document_store/api_versioning_adr.md +0 -52
  193. data/examples/26_document_store/incident_postmortem.md +0 -46
  194. data/examples/26_document_store/postgres_runbook.md +0 -49
  195. data/examples/26_document_store/redis_caching_guide.md +0 -48
  196. data/examples/26_document_store/sidekiq_guide.md +0 -51
  197. data/examples/26_document_store.rb +0 -140
  198. data/examples/27_incident_response/README.md +0 -65
  199. data/examples/27_incident_response/incident_response.rb +0 -261
  200. data/examples/28_mcp_discovery.rb +0 -113
  201. data/examples/29_ractor_tools.rb +0 -240
  202. data/examples/30_ractor_network.rb +0 -252
  203. data/examples/31_launch_assessment.rb +0 -246
  204. data/examples/32_newsletter_reader.rb +0 -193
  205. data/examples/33_stock_generator.rb +0 -80
  206. data/examples/33_stock_predictor.rb +0 -327
  207. data/examples/34_agentskills.rb +0 -76
  208. data/examples/35_hooks.rb +0 -266
  209. data/examples/README.md +0 -550
  210. data/examples/common.rb +0 -180
  211. data/examples/prompts/assistant.md +0 -5
  212. data/examples/prompts/audit_trail.md +0 -8
  213. data/examples/prompts/billing.md +0 -10
  214. data/examples/prompts/classifier.md +0 -7
  215. data/examples/prompts/comedian.md +0 -6
  216. data/examples/prompts/comedy_critic.md +0 -10
  217. data/examples/prompts/configurable.md +0 -9
  218. data/examples/prompts/dispatcher.md +0 -12
  219. data/examples/prompts/entity_extractor.md +0 -14
  220. data/examples/prompts/escalation.md +0 -42
  221. data/examples/prompts/frontmatter_mcp_test.md +0 -9
  222. data/examples/prompts/frontmatter_named_test.md +0 -5
  223. data/examples/prompts/frontmatter_tools_test.md +0 -6
  224. data/examples/prompts/general.md +0 -7
  225. data/examples/prompts/github_assistant.md +0 -14
  226. data/examples/prompts/helper.md +0 -4
  227. data/examples/prompts/incident_responder.md +0 -18
  228. data/examples/prompts/keyword_extractor.md +0 -11
  229. data/examples/prompts/llm_config_demo.md +0 -20
  230. data/examples/prompts/order_support.md +0 -35
  231. data/examples/prompts/os_advocate.md +0 -13
  232. data/examples/prompts/os_chief.md +0 -13
  233. data/examples/prompts/os_editor.md +0 -13
  234. data/examples/prompts/parameterized_main_test.md +0 -6
  235. data/examples/prompts/pii_redactor.md +0 -7
  236. data/examples/prompts/product_support.md +0 -37
  237. data/examples/prompts/runbook_protocol.md +0 -12
  238. data/examples/prompts/sentiment_analyzer.md +0 -12
  239. data/examples/prompts/skill_a_test.md +0 -4
  240. data/examples/prompts/skill_b_test.md +0 -4
  241. data/examples/prompts/skill_config_test.md +0 -5
  242. data/examples/prompts/skill_cycle_a_test.md +0 -6
  243. data/examples/prompts/skill_cycle_b_test.md +0 -6
  244. data/examples/prompts/skill_description_test.md +0 -4
  245. data/examples/prompts/skill_leaf_test.md +0 -4
  246. data/examples/prompts/skill_nested_test.md +0 -6
  247. data/examples/prompts/skill_refs_main_test.md +0 -6
  248. data/examples/prompts/skill_self_ref_test.md +0 -6
  249. data/examples/prompts/skill_with_mcp_test.md +0 -9
  250. data/examples/prompts/skill_with_params_test.md +0 -6
  251. data/examples/prompts/skill_with_robot_name_test.md +0 -5
  252. data/examples/prompts/skill_with_tools_test.md +0 -6
  253. data/examples/prompts/sre_compliance.md +0 -10
  254. data/examples/prompts/structured_output.md +0 -7
  255. data/examples/prompts/synthesizer.md +0 -17
  256. data/examples/prompts/technical.md +0 -10
  257. data/examples/prompts/template_with_skills_test.md +0 -6
  258. data/examples/prompts/triage.md +0 -22
  259. data/examples/ruboruby.md +0 -423
  260. data/examples/run_all.rb +0 -60
  261. data/examples/xyzzy.rb +0 -97
  262. data/lib/robot_lab/mcp/client.rb +0 -223
  263. data/lib/robot_lab/mcp/connection_poller.rb +0 -195
  264. data/lib/robot_lab/mcp/transports/base.rb +0 -64
  265. data/lib/robot_lab/mcp/transports/sse.rb +0 -120
  266. data/lib/robot_lab/mcp/transports/stdio.rb +0 -170
  267. data/lib/robot_lab/mcp/transports/streamable_http.rb +0 -141
  268. data/lib/robot_lab/mcp/transports/websocket.rb +0 -111
  269. data/logfile +0 -8
  270. data/mkdocs.yml +0 -209
  271. data/robot_concurrency.md +0 -38
  272. data/simple_acp_review.md +0 -298
  273. data/temp.md +0 -6
  274. data/tool_manifest_plan.md +0 -155
@@ -1,553 +0,0 @@
1
- # Network
2
-
3
- Orchestrates multiple robots using SimpleFlow pipelines with DAG-based execution.
4
-
5
- ## Class: `RobotLab::Network`
6
-
7
- ```ruby
8
- network = RobotLab.create_network(name: "support", config: config) do
9
- task :classifier, classifier_robot, depends_on: :none
10
- task :billing, billing_robot, depends_on: :optional
11
- end
12
- ```
13
-
14
- `RobotLab.create_network(name:, concurrency: :auto, config: nil, &block)` is the
15
- factory. `Network.new` additionally accepts `memory:` and `parallel_mode:`:
16
-
17
- ```ruby
18
- RobotLab::Network.new(name:, concurrency: :auto, memory: nil, config: nil, parallel_mode: :async, &block)
19
- ```
20
-
21
- There is no `router:` and no `robots:` keyword. Routing is done by subclassing
22
- `Robot`, overriding `#call`, and activating optional tasks — see
23
- [Conditional Routing](#conditional-routing).
24
-
25
- ## Attributes
26
-
27
- ### name
28
-
29
- ```ruby
30
- network.name # => String
31
- ```
32
-
33
- Network identifier for logging and debugging.
34
-
35
- ### robots
36
-
37
- ```ruby
38
- network.robots # => Hash<String, Robot>
39
- ```
40
-
41
- Robots keyed by **String**. The key depends on how the robot was registered:
42
-
43
- | Registered via | Key |
44
- |----------------|-----|
45
- | `task(:analyzer, robot)` | the **task** name — `"analyzer"` — regardless of `robot.name` |
46
- | `add_robot(robot)` | `robot.name` |
47
-
48
- ```ruby
49
- n = RobotLab.create_network(name: "n") do
50
- task :a, RobotLab.build(name: "alpha"), depends_on: :none
51
- end
52
- n.add_robot(RobotLab.build(name: "helper"))
53
-
54
- n.robots.keys # => ["a", "helper"]
55
- n.crew.map(&:name) # => ["alpha", "helper"]
56
- ```
57
-
58
- Examples that assume the two coincide only work when you name the robot after
59
- its task. `result.context` is keyed by the **robot's** name (`@name.to_sym`), not
60
- the task name — another reason to keep them identical.
61
-
62
- ### memory
63
-
64
- ```ruby
65
- network.memory # => Memory
66
- ```
67
-
68
- Shared reactive memory for every robot in the network. It is passed to each robot
69
- on `run()` as `network_memory:`, so in-network robots read and write this instance
70
- instead of their own inherent memory. Created as `Memory.new(network_name: name)`
71
- unless one is supplied to `Network.new(memory:)`.
72
-
73
- ### config
74
-
75
- ```ruby
76
- network.config # => RunConfig
77
- ```
78
-
79
- Shared operational defaults. Passed to robots during `run()` as `network_config:`.
80
-
81
- !!! warning "Only `mcp` and `tools` propagate to member robots"
82
- LLM fields (`model`, `temperature`, `max_tokens`, …) and callbacks
83
- (`on_content`, `on_tool_call`, `on_tool_result`) are read from each robot's
84
- own config at construction time and are **never** inherited from the
85
- network. A member robot picks up the network's `mcp`/`tools` only when it
86
- opts in with `:inherit`. `max_concurrent_robots` is the one field the
87
- network itself consumes (it is passed to `pipeline.call_parallel`).
88
-
89
- ### parallel_mode
90
-
91
- ```ruby
92
- network.parallel_mode # => :async (default) or :ractor
93
- ```
94
-
95
- Execution strategy for `run`. `:async` uses the SimpleFlow pipeline;
96
- `:ractor` routes through `RactorNetworkScheduler` and **raises
97
- `RobotLab::DependencyError`** unless the `robot_lab-ractor` gem is loaded. Set via
98
- `Network.new(parallel_mode:)`.
99
-
100
- ### hooks
101
-
102
- ```ruby
103
- network.hooks # => RobotLab::HookRegistry
104
- ```
105
-
106
- The network's own hook registry, populated by [`network.on`](#on). Consulted for
107
- network-run, robot-run, and task hooks alongside `RobotLab.hooks`.
108
-
109
- ### pipeline
110
-
111
- ```ruby
112
- network.pipeline # => SimpleFlow::Pipeline
113
- ```
114
-
115
- The underlying SimpleFlow pipeline.
116
-
117
- ## Methods
118
-
119
- ### run
120
-
121
- ```ruby
122
- result = network.run(
123
- message: "Help me",
124
- customer_id: 123,
125
- **context
126
- )
127
- # => SimpleFlow::Result
128
-
129
- # Runnable protocol: a positional message works too, just like Robot#run
130
- result = network.run("Help me", customer_id: 123)
131
- ```
132
-
133
- Execute the network pipeline.
134
-
135
- **Parameters:**
136
-
137
- | Name | Type | Description |
138
- |------|------|-------------|
139
- | `message` | `String`, `nil` | The input message — as the `message:` keyword, or positionally (folded into `message:` when given) |
140
- | `**context` | `Hash` | Additional context passed to all robots |
141
-
142
- `run` injects three keys into the run context before executing:
143
- `network_memory:` (the shared `Memory`), `network:` (the network itself), and
144
- `network_config:` (only when the network's `RunConfig` is non-empty). The whole
145
- execution is wrapped in the `:network_run` hook family.
146
-
147
- **Returns:** `SimpleFlow::Result` — except under `parallel_mode: :ractor`, where
148
- `RactorNetworkScheduler#run_pipeline` returns its own results structure.
149
-
150
- !!! note "Member robots still default to `tools: :none`"
151
- Task-level `tools:`/`mcp:` default to `:none` just like `Robot#run`. Declare
152
- the task with `tools: :inherit` (or an explicit name array) for its robot to
153
- receive any tools.
154
-
155
- ### task
156
-
157
- ```ruby
158
- network.task(name, robot,
159
- context: {}, mcp: :none, tools: :none, memory: nil,
160
- config: nil, depends_on: :none, poller_group: :default)
161
- # => self
162
- ```
163
-
164
- Add a task to the pipeline with optional per-task configuration.
165
-
166
- **Parameters:**
167
-
168
- | Name | Type | Default | Description |
169
- |------|------|---------|-------------|
170
- | `name` | `Symbol` | **required** | Task identifier; also the key under which the robot is stored in `network.robots` |
171
- | `robot` | `Robot` | **required** | Robot instance to execute |
172
- | `context` | `Hash` | `{}` | Task-specific context (deep-merged with run params) |
173
- | `mcp` | `Symbol`, `Array` | `:none` | MCP server config for this task |
174
- | `tools` | `Symbol`, `Array` | `:none` | Tools config for this task — tool **names**, not instances |
175
- | `memory` | `Memory`, `Hash`, `nil` | `nil` | Task-specific memory, overriding the network's shared memory |
176
- | `config` | `RunConfig`, `nil` | `nil` | Per-task config, merged on top of the network's RunConfig. Like the network config, only `mcp`/`tools` reach the robot |
177
- | `depends_on` | `:none`, `Array<Symbol>`, `:optional` | `:none` | Task dependencies |
178
- | `poller_group` | `Symbol` | `:default` | Bus-poller group for this robot; the network registers the group on its shared `BusPoller` and assigns it to the robot |
179
-
180
- **Dependency Types:**
181
-
182
- | Value | Description |
183
- |-------|-------------|
184
- | `:none` | No dependencies, runs first |
185
- | `[:task1, :task2]` | Waits for listed tasks to complete |
186
- | `:optional` | Only runs when explicitly activated via `result.activate(:name)` |
187
-
188
- ### parallel
189
-
190
- ```ruby
191
- network.parallel(name = nil, depends_on: :none) { ... }
192
- # => self
193
- ```
194
-
195
- Declare a named group of steps that run concurrently, then depend on the group as
196
- a unit. Delegates directly to `SimpleFlow::Pipeline#parallel`.
197
-
198
- **Parameters:**
199
-
200
- | Name | Type | Default | Description |
201
- |------|------|---------|-------------|
202
- | `name` | `Symbol`, `nil` | `nil` | Optional name for the group, usable in a later `depends_on:` |
203
- | `depends_on` | `Symbol`, `Array` | `:none` | Dependencies for the group as a whole |
204
-
205
- !!! warning "Use `step` inside the block, not `task`"
206
- The block is `instance_eval`'d by `SimpleFlow::Pipeline::ParallelBlock`,
207
- whose only DSL methods are `step` and `steps` — **not** `task`. This is an
208
- inconsistency with the top-level `Network` DSL. Inner entries therefore also
209
- bypass `Network#task`, so they are not registered in `network.robots`, get no
210
- per-task `context:`/`tools:`/`memory:`, and are not assigned a poller group.
211
-
212
- ```ruby
213
- network.parallel :fetch_data, depends_on: :validate do
214
- step :fetch_orders, orders_robot
215
- step :fetch_products, products_robot
216
- end
217
- network.task :process, processor, depends_on: :fetch_data
218
- ```
219
-
220
- Declaring the concurrent robots as ordinary `task`s that share a `depends_on:`
221
- avoids all of that and is the pattern used throughout these docs.
222
-
223
- ### broadcast
224
-
225
- ```ruby
226
- network.broadcast(payload)
227
- # => self
228
- ```
229
-
230
- Send a network-wide announcement. Wraps `payload` as
231
- `{ payload:, network:, timestamp: }`, dispatches it asynchronously to every
232
- handler registered with [`on_broadcast`](#on_broadcast), and also writes it to
233
- shared memory under `Network::BROADCAST_KEY` (`:_network_broadcast`) so robots can
234
- pick it up with `memory.subscribe(:_network_broadcast)`.
235
-
236
- ```ruby
237
- network.broadcast(event: :pause, reason: "rate limit hit")
238
- ```
239
-
240
- ### on_broadcast
241
-
242
- ```ruby
243
- network.on_broadcast { |message| ... }
244
- # => self
245
- ```
246
-
247
- Register a handler for `broadcast` messages. The block receives the full envelope
248
- (`message[:payload]`, `message[:network]`, `message[:timestamp]`).
249
-
250
- **Raises:** `ArgumentError` if no block is given.
251
-
252
- ```ruby
253
- network.on_broadcast do |message|
254
- pause_current_work if message[:payload][:event] == :pause
255
- end
256
- ```
257
-
258
- ### reset_memory
259
-
260
- ```ruby
261
- network.reset_memory
262
- # => self
263
- ```
264
-
265
- Reset the network's shared memory to its initial state (`Memory#reset`). Useful
266
- between runs. This clears the key-value store only; it does not touch any robot's
267
- chat history.
268
-
269
- ### on
270
-
271
- ```ruby
272
- network.on(HandlerClass, context: nil)
273
- ```
274
-
275
- Register a hook handler on the network's own registry (`network.hooks`). Unlike
276
- `robot.on`, handlers registered here **do** fire for the `:task` hook family,
277
- which resolves against `[RobotLab.hooks, network&.hooks]`.
278
-
279
- ### to_dot
280
-
281
- ```ruby
282
- network.to_dot # => String, or nil
283
- ```
284
-
285
- Graphviz DOT representation of the pipeline (`pipeline.visualize_dot`).
286
-
287
- ### add_robot
288
-
289
- ```ruby
290
- network.add_robot(robot)
291
- # => self
292
- ```
293
-
294
- Add a robot without creating a pipeline task. Useful for robots referenced by other tasks.
295
-
296
- ### remove_robot
297
-
298
- ```ruby
299
- network.remove_robot("billing")
300
- # => Robot, or nil if no robot by that name was present
301
- ```
302
-
303
- Remove a dynamically-added robot from the crew by name. Complements `add_robot`. Only removes the robot from `@robots` — it does **not** rewrite the pipeline, so don't remove a robot that is a pipeline task (a `depends_on` reference to a removed robot's task name would then fail).
304
-
305
- ### robot / []
306
-
307
- ```ruby
308
- network.robot("billing") # => Robot
309
- network["billing"] # => Robot (alias)
310
- ```
311
-
312
- Get robot by name.
313
-
314
- ### available_robots
315
-
316
- ```ruby
317
- network.available_robots # => Array<Robot>
318
- ```
319
-
320
- Returns all robot instances.
321
-
322
- ### visualize
323
-
324
- ```ruby
325
- network.visualize # => String
326
- ```
327
-
328
- ASCII visualization of the pipeline.
329
-
330
- ### to_mermaid
331
-
332
- ```ruby
333
- network.to_mermaid # => String
334
- ```
335
-
336
- Mermaid diagram definition.
337
-
338
- ### execution_plan
339
-
340
- ```ruby
341
- network.execution_plan # => String
342
- ```
343
-
344
- Human-readable execution plan.
345
-
346
- ### to_h
347
-
348
- ```ruby
349
- network.to_h # => Hash
350
- ```
351
-
352
- Hash representation of network configuration.
353
-
354
- ```ruby
355
- {
356
- name: "support",
357
- robots: ["classifier", "billing", "technical"], # keys of network.robots
358
- tasks: ["classifier", "billing", "technical"], # task names only
359
- optional_tasks: [:billing, :technical],
360
- config: { model: "claude-sonnet-4", temperature: 0.7 }
361
- }
362
- ```
363
-
364
- The hash is `.compact`ed and `config` is omitted entirely when the network's
365
- `RunConfig` is empty. `config` comes from `RunConfig#to_json_hash`, so the
366
- non-serializable fields (`on_tool_call`, `on_tool_result`, `on_content`, `bus`,
367
- `auto_compact`) are excluded. `robots` includes robots added with `add_robot`;
368
- `tasks` does not.
369
-
370
- ## SimpleFlow::Result
371
-
372
- `Network#run` returns a `SimpleFlow::Result`. This is its **complete** public API
373
- (simple_flow 0.4):
374
-
375
- | Method | Description |
376
- |--------|-------------|
377
- | `value` | The final task's output (a `RobotResult`) |
378
- | `context` | Hash of accumulated context, including every task's result |
379
- | `continue?` | `true` while the pipeline is still running steps |
380
- | `continue(value)` | Returns a new Result carrying `value`, still continuing |
381
- | `halt(value)` | Returns a new Result that stops the pipeline |
382
- | `with_context(key, value)` | Returns a new Result with an added context entry |
383
- | `with_error(key, message)` | Returns a new Result with an added error — **both arguments are required** |
384
- | `errors` | Accumulated errors |
385
- | `activate(step_name)` | Marks an `:optional` step to run |
386
- | `activated_steps` | The set of optional steps activated so far |
387
-
388
- !!! danger "`halted?`, `continued?`, and `with_value` do not exist"
389
- They raise `NoMethodError`. The predicate is `continue?` — a halted result
390
- is one where `continue?` is `false`.
391
-
392
- ```ruby
393
- result = network.run(message: "Hello")
394
- puts "stopped early" unless result.continue?
395
- ```
396
-
397
- ### Context Structure
398
-
399
- ```ruby
400
- result.context[:run_params] # Original run parameters
401
- result.context[:classifier] # RobotResult from the robot NAMED "classifier"
402
- result.context[:billing] # RobotResult from the robot NAMED "billing"
403
- ```
404
-
405
- `Robot#call` writes its result with `result.with_context(@name.to_sym, robot_result)`,
406
- so context keys are **robot names**, not task names. Name each robot after its
407
- task to keep the two aligned.
408
-
409
- ## Builder DSL
410
-
411
- ### task
412
-
413
- ```ruby
414
- network = RobotLab.create_network(name: "pipeline") do
415
- task :first, robot1, depends_on: :none
416
- task :second, robot2, depends_on: [:first]
417
- task :optional, robot3, depends_on: :optional
418
- end
419
- ```
420
-
421
- ### task with context
422
-
423
- ```ruby
424
- network = RobotLab.create_network(name: "support") do
425
- task :classifier, classifier_robot, depends_on: :none
426
- task :billing, billing_robot,
427
- context: { department: "billing", escalation_level: 2 },
428
- depends_on: :optional
429
- task :technical, technical_robot,
430
- context: { department: "technical" },
431
- # Entries must match how the robot attached each tool. If technical_robot
432
- # was built with local_tools: [DebugTool, LogTool] (classes), use the class
433
- # names; if it attached instances, use %w[debug log] instead.
434
- tools: %w[DebugTool LogTool],
435
- depends_on: :optional
436
- end
437
- ```
438
-
439
- `tools:` here is a **name allowlist**, exactly as on `Robot`. Attach the tool
440
- objects themselves with `local_tools:` when building the robot.
441
-
442
- ## Examples
443
-
444
- ### Sequential Pipeline
445
-
446
- ```ruby
447
- network = RobotLab.create_network(name: "pipeline") do
448
- task :extract, extractor, depends_on: :none
449
- task :transform, transformer, depends_on: [:extract]
450
- task :load, loader, depends_on: [:transform]
451
- end
452
-
453
- result = network.run(message: "Process this document")
454
- puts result.value.last_text_content
455
- ```
456
-
457
- ### Parallel Execution
458
-
459
- ```ruby
460
- network = RobotLab.create_network(name: "analysis", concurrency: :threads) do
461
- task :fetch, fetcher, depends_on: :none
462
-
463
- # Run in parallel
464
- task :sentiment, sentiment_bot, depends_on: [:fetch]
465
- task :entities, entity_bot, depends_on: [:fetch]
466
-
467
- # Wait for both
468
- task :merge, merger, depends_on: [:sentiment, :entities]
469
- end
470
- ```
471
-
472
- ### Conditional Routing
473
-
474
- There is no `Router` class and no `Router::Args`. Conditional routing is done by
475
- subclassing `Robot`, overriding `#call`, and calling `result.activate(:task_name)`
476
- for tasks declared `depends_on: :optional`:
477
-
478
- ```ruby
479
- class ClassifierRobot < RobotLab::Robot
480
- def call(result)
481
- run_context = extract_run_context(result)
482
- message = run_context.delete(:message) # must be positional
483
- robot_result = run(message, **run_context)
484
-
485
- new_result = result
486
- .with_context(@name.to_sym, robot_result)
487
- .continue(robot_result)
488
-
489
- category = robot_result.last_text_content.to_s.downcase
490
- case category
491
- when /billing/ then new_result.activate(:billing)
492
- when /technical/ then new_result.activate(:technical)
493
- else new_result.activate(:general)
494
- end
495
- end
496
- end
497
-
498
- network = RobotLab.create_network(name: "support") do
499
- task :classifier, ClassifierRobot.new(name: "classifier", template: :classifier),
500
- depends_on: :none
501
- task :billing, billing_robot, depends_on: :optional
502
- task :technical, technical_robot, depends_on: :optional
503
- task :general, general_robot, depends_on: :optional
504
- end
505
-
506
- result = network.run(message: "I have a billing question")
507
- puts result.value.last_text_content
508
- ```
509
-
510
- ### Accessing Task Results
511
-
512
- ```ruby
513
- result = network.run(message: "Hello")
514
-
515
- # Access individual task results
516
- classifier_result = result.context[:classifier]
517
- puts "Classification: #{classifier_result.last_text_content}"
518
-
519
- # Check which optional task ran
520
- if result.context[:billing]
521
- puts "Billing handled the request"
522
- elsif result.context[:technical]
523
- puts "Technical handled the request"
524
- end
525
- ```
526
-
527
- ## Runnable Protocol
528
-
529
- `Network` includes `RobotLab::Runnable`, the shared interface it has in common with `Robot` — see [Runnable Protocol](../../architecture/network-orchestration.md#runnable-protocol) for the full picture.
530
-
531
- | Method | Returns |
532
- |--------|---------|
533
- | `crew` | `robots.values` — the constituent robots, as an `Array`, in pipeline order |
534
- | `chief` | `crew.first` — the lead robot |
535
- | `robot_count` | `crew.size` |
536
- | `network?` | `true` |
537
- | `single?` | `false` |
538
-
539
- ```ruby
540
- network.crew.map(&:name) # => the ROBOTS' names, not the task keys
541
- network.chief # => the first robot
542
- network.network? # => true
543
- ```
544
-
545
- `crew` returns `robots.values` — robot *instances*. So `crew.map(&:name)` yields
546
- `robot.name` for each, which differs from `network.robots.keys` (task names)
547
- unless each robot is named after its task.
548
-
549
- ## See Also
550
-
551
- - [Creating Networks Guide](../../guides/creating-networks.md)
552
- - [Network Orchestration](../../architecture/network-orchestration.md)
553
- - [Robot](robot.md)