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,478 +0,0 @@
1
- # Network Orchestration
2
-
3
- Networks coordinate multiple robots using [SimpleFlow](https://github.com/MadBomber/simple_flow) pipelines for DAG-based execution.
4
-
5
- ## Network Structure
6
-
7
- A network is a thin wrapper around `SimpleFlow::Pipeline`:
8
-
9
- - **Pipeline**: DAG-based execution engine
10
- - **Robots**: Named collection of robot instances
11
- - **Tasks**: Wrap robots with per-task configuration and define dependencies
12
- - **Memory**: Shared reactive memory for inter-robot communication
13
-
14
- ```ruby
15
- network = RobotLab.create_network(name: "customer_service") do
16
- task :classifier, classifier_robot, depends_on: :none
17
- task :billing, billing_robot, depends_on: :optional
18
- task :technical, technical_robot, depends_on: :optional
19
- end
20
- ```
21
-
22
- ## Runnable Protocol
23
-
24
- `Network` implements `RobotLab::Runnable` — the same interface `Robot` implements — so code that needs to run "a robot or a network" doesn't have to branch on `is_a?(RobotLab::Network)`. For a `Network`: `crew` returns `robots.values` (pipeline order), `chief` is `crew.first`, `robot_count` is `crew.size`, and `network?` is `true`. `run(message = nil, **opts)` accepts a positional message the same way `Robot#run` does — it's folded into `message:` — while the existing `run(message: ...)` keyword form still works unchanged. See [Runnable Protocol](../architecture/core-concepts.md#runnable-protocol) in Core Concepts for the full comparison against `Robot`.
25
-
26
- ## Creating Networks
27
-
28
- Networks are created via `RobotLab.create_network` with a block DSL:
29
-
30
- ```ruby
31
- analyst = RobotLab.build(name: "analyst", system_prompt: "Analyze the input.")
32
- writer = RobotLab.build(name: "writer", system_prompt: "Write a report.")
33
- reviewer = RobotLab.build(name: "reviewer", system_prompt: "Review the report.")
34
-
35
- network = RobotLab.create_network(name: "pipeline") do
36
- task :analyst, analyst, depends_on: :none
37
- task :writer, writer, depends_on: [:analyst]
38
- task :reviewer, reviewer, depends_on: [:writer]
39
- end
40
-
41
- result = network.run(message: "Analyze this quarterly data")
42
- ```
43
-
44
- ## Task Configuration
45
-
46
- Tasks can have per-task configuration that is deep-merged with network run params:
47
-
48
- ```ruby
49
- # The allowlist below matches because the tool was attached as a CLASS.
50
- billing_robot = RobotLab.build(name: "billing", system_prompt: "...",
51
- local_tools: [RefundTool])
52
-
53
- network = RobotLab.create_network(name: "support") do
54
- task :classifier, classifier_robot, depends_on: :none
55
- task :billing, billing_robot,
56
- context: { department: "billing", escalation_level: 2 },
57
- tools: [RefundTool],
58
- depends_on: :optional
59
- task :technical, technical_robot,
60
- context: { department: "technical" },
61
- mcp: [filesystem_server],
62
- depends_on: :optional
63
- end
64
- ```
65
-
66
- !!! warning "An explicit `tools:` array must match the attachment form"
67
- `ToolConfig.filter_tools` selects with `allowed_set.include?(tool_name(tool))`,
68
- and `tool_name` is just `tool.name.to_s`. But `Class#name` and
69
- `RubyLLM::Tool#name` return different strings, so the entry you list has to
70
- match how the tool was attached:
71
-
72
- | Attached as | Allowlist entry | Result |
73
- |-------------|-----------------|--------|
74
- | `local_tools: [RefundTool]` (class) | `tools: [RefundTool]` → `"RefundTool"` | matches |
75
- | `local_tools: [RefundTool]` (class) | `tools: %w[refund]` | no match |
76
- | `local_tools: [RefundTool.new]` (instance) | `tools: [RefundTool]` | no match |
77
- | `local_tools: [RefundTool.new]` (instance) | `tools: %w[refund]` | matches |
78
-
79
- Both forms work; mixing them silently yields an empty tool list. Pick one
80
- convention per robot and keep the allowlist in the same form.
81
-
82
- A task's `tools:` is *not* validated — it lands in `run_params` and reaches
83
- the robot as a runtime value. The robot **constructor**'s `tools:` kwarg is
84
- validated by `validate_tools_filter!` and raises `ArgumentError` for
85
- anything that is not a String or Symbol, so the class form shown above is
86
- usable only at the task/`run` level.
87
-
88
- ### Task Parameters
89
-
90
- | Parameter | Type | Description |
91
- |-----------|------|-------------|
92
- | `name` | Symbol | Task/step name |
93
- | `robot` | Robot | The robot instance |
94
- | `context` | Hash | Task-specific context (deep-merged with run params) |
95
- | `mcp` | Symbol, Array | MCP server config (`:none`, `:inherit`, or array) |
96
- | `tools` | Symbol, Array | Tools config (`:none`, `:inherit`, or array) |
97
- | `memory` | Memory, Hash, nil | Task-specific memory |
98
- | `config` | RunConfig, nil | Per-task RunConfig, merged into the network config the robot sees (`mcp`/`tools` only in practice) |
99
- | `depends_on` | Symbol, Array | Dependencies (`:none`, `:optional`, or task names) |
100
- | `poller_group` | Symbol | Bus poller group label for this robot (default `:default`; purely organizational) |
101
-
102
- `mcp:` and `tools:` default to `:none` here. Anything other than `:none` is written into `run_params` and reaches the robot as its **runtime** value, so it is resolved against the robot's build-time config exactly as if it had been passed to `run`.
103
-
104
- ## Execution Model
105
-
106
- ```mermaid
107
- stateDiagram-v2
108
- [*] --> Start
109
- Start --> ExecuteTask: next ready task
110
- ExecuteTask --> CheckDependents: task complete
111
- CheckDependents --> ExecuteTask: more tasks ready
112
- CheckDependents --> Complete: all tasks done
113
- ExecuteTask --> Halted: task halts
114
- Complete --> [*]
115
- Halted --> [*]
116
- ```
117
-
118
- ### Task Dependency Types
119
-
120
- | Type | Description |
121
- |------|-------------|
122
- | `:none` | No dependencies, runs first |
123
- | `[:task1, :task2]` | Waits for listed tasks to complete |
124
- | `:optional` | Only runs when explicitly activated |
125
-
126
- ## Robot#call Interface
127
-
128
- Each robot implements the SimpleFlow step interface via `call(result)`:
129
-
130
- ```ruby
131
- # Inside Robot (simplified -- timing and the rescue are elided)
132
- def call(result)
133
- run_context = extract_run_context(result)
134
- message = run_context.delete(:message)
135
-
136
- robot_result = run(message, **run_context)
137
- robot_result.duration = ... # monotonic elapsed seconds
138
-
139
- result
140
- .with_context(@name.to_sym, robot_result) # keyed by the ROBOT's name
141
- .continue(robot_result)
142
- end
143
- ```
144
-
145
- The real method also wraps the body so that any exception — including non-`StandardError` ones — is turned into a `RobotResult` whose text is `"Error: <class>: <message>"`, so one failing robot does not crash the pipeline.
146
-
147
- ### extract_run_context
148
-
149
- The `extract_run_context` method pulls parameters from the SimpleFlow result:
150
-
151
- - Deletes `:mcp`, `:tools`, `:memory`, `:network_memory`, `:network_config`, `:network`, and `:task` out of `run_params`, then re-attaches them as explicit keyword arguments to `run`
152
- - `:mcp` and `:tools` default to `:none` when the task did not set them — matching `run`'s own defaults
153
- - Merges the current result value into the remaining context
154
- - If the previous result value is a `RobotResult`, uses its `last_text_content` as the message
155
- - If it is a String, uses it directly as the message
156
- - If it is a Hash, merges it into the context
157
- - Anything else is coerced with `to_s` and used as the message
158
-
159
- ## Task#call Interface
160
-
161
- Each `Task` wraps a robot and enhances the SimpleFlow result before delegation:
162
-
163
- ```ruby
164
- # Inside Task (simplified)
165
- def call(result)
166
- context = TaskHookContext.new(network: @network, task: self, robot: @robot,
167
- memory: @memory || @network&.memory, config: @config)
168
-
169
- RobotLab::Hooks.run(:task, context, registries: [RobotLab.hooks, @network&.hooks]) do
170
- @robot.call(enhanced_result(result))
171
- end
172
- end
173
-
174
- def enhanced_result(result)
175
- run_params = deep_merge(result.context[:run_params] || {}, @context)
176
-
177
- run_params[:mcp] = @mcp unless @mcp == :none
178
- run_params[:tools] = @tools unless @tools == :none
179
- run_params[:memory] = @memory if @memory
180
-
181
- # Back-references the robot needs for hooks and config resolution
182
- run_params[:task] = self
183
- run_params[:network] = @network if @network
184
-
185
- if @config
186
- network_rc = run_params[:network_config]
187
- run_params[:network_config] = network_rc ? network_rc.merge(@config) : @config
188
- end
189
-
190
- result.with_context(:run_params, run_params)
191
- end
192
- ```
193
-
194
- Two things to note. The `:task` hook family is dispatched against `[RobotLab.hooks, network&.hooks]` only — a handler registered with `robot.on` never fires for task hooks. And the task's own `config:` is merged into `run_params[:network_config]`, which is the value `Robot#resolve_mcp_hierarchy` / `#resolve_tools_hierarchy` consult as the parent level.
195
-
196
- ## SimpleFlow::Result
197
-
198
- The result object flows through the pipeline:
199
-
200
- ```ruby
201
- result.value # Current task's output (RobotResult)
202
- result.context # Accumulated context from all tasks
203
- result.continue? # Whether execution continues (the only status predicate)
204
- ```
205
-
206
- ### Result Methods
207
-
208
- This is the complete public API of `SimpleFlow::Result` (simple_flow 0.4):
209
-
210
- | Method | Description |
211
- |--------|-------------|
212
- | `value` | Current value flowing through the pipeline |
213
- | `context` | Accumulated context hash |
214
- | `continue(value)` | Continue to next tasks |
215
- | `continue?` | Whether the pipeline is still continuing |
216
- | `halt(value)` | Stop pipeline execution |
217
- | `with_context(key, val)` | Add data to context |
218
- | `with_error(key, message)` | Record an error (both arguments required) |
219
- | `errors` | Recorded errors |
220
- | `activate(task_name)` | Enable an optional task |
221
- | `activated_steps` | Optional tasks that have been activated |
222
-
223
- There is no `halted?`, no `continued?`, and no `with_value` — use `continue?` for status and `continue(value)` to set a new value.
224
-
225
- ### Context Structure
226
-
227
- ```ruby
228
- {
229
- run_params: { message: "...", customer_id: 123,
230
- network_memory: memory, network: network, task: task },
231
- classifier: RobotResult, # Stored by Robot#call under the ROBOT's name
232
- billing: RobotResult,
233
- # ... other robot results
234
- }
235
- ```
236
-
237
- `Robot#call` stores its output with `result.with_context(@name.to_sym, robot_result)` — the key is the **robot's** `name`, not the task name. The two coincide only when you name them identically:
238
-
239
- ```ruby
240
- worker = RobotLab.build(name: "worker_bot", system_prompt: "...")
241
-
242
- net = RobotLab.create_network(name: "n") do
243
- task :analysis, worker, depends_on: :none
244
- end
245
-
246
- res = net.run(message: "hi")
247
- res.context.keys #=> [:run_params, :worker_bot] -- not :analysis
248
- ```
249
-
250
- ## Optional Task Activation
251
-
252
- Optional tasks (those with `depends_on: :optional`) do not run automatically. They must be activated by a preceding task using `result.activate(:task_name)`.
253
-
254
- This pattern is commonly used with a classifier robot that analyzes the input and routes to the appropriate handler:
255
-
256
- ```ruby
257
- classifier = RobotLab.build(
258
- name: "classifier",
259
- system_prompt: "Classify the request. Respond with: BILLING, TECHNICAL, or GENERAL."
260
- )
261
-
262
- billing = RobotLab.build(name: "billing", system_prompt: "Handle billing requests.")
263
- technical = RobotLab.build(name: "technical", system_prompt: "Handle technical requests.")
264
- general = RobotLab.build(name: "general", system_prompt: "Handle general requests.")
265
-
266
- network = RobotLab.create_network(name: "support") do
267
- task :classifier, classifier, depends_on: :none
268
- task :billing, billing, depends_on: :optional
269
- task :technical, technical, depends_on: :optional
270
- task :general, general, depends_on: :optional
271
- end
272
- ```
273
-
274
- ### Classifier Robot Pattern
275
-
276
- To activate optional tasks, a robot subclass overrides `call` to inspect its own output and activate the appropriate downstream task:
277
-
278
- ```ruby
279
- class ClassifierRobot < RobotLab::Robot
280
- def call(result)
281
- run_context = extract_run_context(result)
282
- message = run_context.delete(:message)
283
-
284
- robot_result = run(message, **run_context)
285
-
286
- new_result = result
287
- .with_context(@name.to_sym, robot_result)
288
- .continue(robot_result)
289
-
290
- # Analyze output and activate the appropriate optional task
291
- category = robot_result.last_text_content.to_s.downcase
292
-
293
- case category
294
- when /billing/
295
- new_result.activate(:billing)
296
- when /technical/
297
- new_result.activate(:technical)
298
- else
299
- new_result.activate(:general)
300
- end
301
- end
302
- end
303
- ```
304
-
305
- ## Shared Memory
306
-
307
- All robots in a network share the network's memory during execution. The network injects its memory into the run context:
308
-
309
- ```ruby
310
- # Inside Network#run
311
- def run(message = nil, **run_context)
312
- run_context[:message] = message unless message.nil? # Runnable protocol
313
-
314
- run_context[:network_memory] = @memory
315
- run_context[:network] = self
316
- run_context[:network_config] = @config unless @config.empty?
317
-
318
- context = NetworkRunHookContext.new(network: self, context: run_context,
319
- memory: @memory, config: @config)
320
-
321
- RobotLab::Hooks.run(:network_run, context, registries: [RobotLab.hooks, @hooks]) do
322
- if @parallel_mode == :ractor
323
- run_with_ractor_scheduler(context.context)
324
- else
325
- initial_result = SimpleFlow::Result.new(
326
- context.context,
327
- context: { run_params: context.context }
328
- )
329
- @pipeline.call_parallel(initial_result, max_concurrent: @config.max_concurrent_robots)
330
- end
331
- end
332
- end
333
- ```
334
-
335
- Beyond injecting the shared memory, this does four things worth knowing:
336
-
337
- - It puts the network itself and (when non-empty) the network's `RunConfig` into `run_params`, which is how robots find the parent level for `mcp`/`tools` resolution.
338
- - The whole run is wrapped in the `:network_run` hook, dispatched against `[RobotLab.hooks, network.hooks]`.
339
- - `parallel_mode: :ractor` routes to `run_with_ractor_scheduler` instead of the SimpleFlow pipeline; it raises `RobotLab::DependencyError` unless the `robot_lab-ractor` gem is loaded.
340
- - `max_concurrent:` comes from `@config.max_concurrent_robots` — the one `RunConfig` field the network itself consumes.
341
-
342
- Robots use the shared memory for inter-robot communication:
343
-
344
- ```ruby
345
- # Robot A writes to shared memory
346
- memory.set(:classification, "billing")
347
-
348
- # Robot B reads from shared memory
349
- category = memory.get(:classification, wait: true)
350
- ```
351
-
352
- See [Memory Management](state-management.md) for details on reactive features like subscriptions and blocking reads.
353
-
354
- ## Broadcasting
355
-
356
- Networks support a broadcast channel for network-wide announcements:
357
-
358
- ```ruby
359
- # Register handlers
360
- network.on_broadcast do |message|
361
- case message[:payload][:event]
362
- when :pause
363
- pause_current_work
364
- when :resume
365
- resume_work
366
- end
367
- end
368
-
369
- # Send broadcasts
370
- network.broadcast(event: :pause, reason: "rate limit hit")
371
- network.broadcast(event: :phase_complete, phase: "analysis")
372
- ```
373
-
374
- Each handler is invoked inside an `Async { }` block, and the message is also written to memory at the `_network_broadcast` key (`Network::BROADCAST_KEY`), so robots can subscribe via `memory.subscribe(:_network_broadcast)`. Outside a running reactor, `Async { }` runs the block synchronously on the caller's thread. The message handed to a handler is `{ payload:, network:, timestamp: }`, which is why the examples above reach for `message[:payload][:event]`.
375
-
376
- ## Parallel Execution
377
-
378
- Tasks with the same dependencies can run in parallel:
379
-
380
- ```ruby
381
- network = RobotLab.create_network(name: "analysis", concurrency: :threads) do
382
- task :fetch, fetcher, depends_on: :none
383
-
384
- # These three run in parallel after :fetch completes
385
- task :sentiment, sentiment_bot, depends_on: [:fetch]
386
- task :entities, entity_bot, depends_on: [:fetch]
387
- task :keywords, keyword_bot, depends_on: [:fetch]
388
-
389
- # Waits for all three
390
- task :merge, merger, depends_on: [:sentiment, :entities, :keywords]
391
- end
392
- ```
393
-
394
- ### Concurrency Modes
395
-
396
- `create_network`'s `concurrency:` is passed straight to `SimpleFlow::Pipeline`:
397
-
398
- | Mode | Description |
399
- |------|-------------|
400
- | `:auto` | SimpleFlow chooses best mode (default) |
401
- | `:threads` | Use Ruby threads |
402
- | `:async` | Use async/fiber |
403
-
404
- The number of tasks running at once is capped by the network config's `max_concurrent_robots`, which `Network#run` passes to `call_parallel` as `max_concurrent:`.
405
-
406
- Separately, `Network.new(parallel_mode:)` selects the execution backend. It defaults to `:async` (the SimpleFlow pipeline above). Setting `parallel_mode: :ractor` bypasses SimpleFlow entirely for a Ractor-based scheduler and requires the `robot_lab-ractor` gem — without it, `run` raises `RobotLab::DependencyError`.
407
-
408
- ## Data Flow
409
-
410
- 1. **Initial Value**: `network.run(message, **params)` creates an initial `SimpleFlow::Result` with the run context
411
- 2. **Run Params**: Stored in `result.context[:run_params]`, including `network_memory`, `network`, and (per task) `task`
412
- 3. **Task Results**: Each robot adds its `RobotResult` to context under **its own `name`**
413
- 4. **Final Value**: Last task's output becomes `result.value`
414
-
415
- ```ruby
416
- result = network.run(
417
- message: "Help with billing",
418
- customer_id: 123
419
- )
420
-
421
- result.context[:run_params] #=> { message: "...", customer_id: 123,
422
- # network_memory: ..., network: ..., task: ... }
423
- result.context[:classifier] #=> RobotResult from the robot NAMED "classifier"
424
- result.context[:billing] #=> RobotResult from the robot NAMED "billing"
425
- result.value #=> Final RobotResult
426
- ```
427
-
428
- If a robot's `name:` differs from the task name it was registered under, look it up by the robot's name.
429
-
430
- ## Visualization
431
-
432
- Networks provide visualization methods via the underlying SimpleFlow pipeline:
433
-
434
- ```ruby
435
- # ASCII representation
436
- puts network.visualize
437
-
438
- # Mermaid diagram
439
- puts network.to_mermaid
440
-
441
- # DOT format (Graphviz)
442
- puts network.to_dot
443
-
444
- # Execution plan description
445
- puts network.execution_plan
446
- ```
447
-
448
- ## Network Inspection
449
-
450
- ```ruby
451
- # Get a robot by its registration key
452
- network.robot(:classifier) #=> Robot
453
- network[:classifier] #=> Robot (alias)
454
-
455
- # List all robots
456
- network.available_robots #=> [Robot, Robot, ...]
457
- network.crew #=> same array, via the Runnable protocol
458
-
459
- # Add a robot without a task
460
- network.add_robot(extra_robot)
461
-
462
- # Remove a dynamically-added robot (returns it, or nil if absent).
463
- # Only affects the crew (@robots) -- does not rewrite the pipeline.
464
- network.remove_robot(:extra_robot)
465
-
466
- # Convert to hash
467
- network.to_h
468
- #=> { name: "support", robots: ["classifier", "billing"],
469
- # tasks: ["classifier", "billing"], optional_tasks: [],
470
- # config: { ... } } # :config present only when the network config is non-empty
471
- ```
472
-
473
- The `@robots` hash is keyed **by task name** for robots registered with `task`, but **by `robot.name`** for robots added with `add_robot`. So for a robot built as `name: "worker_bot"` and registered as `task :analysis, worker`, `network.robot(:analysis)` returns it and `network.robot(:worker_bot)` returns `nil` — the opposite of how `result.context` is keyed. `to_h[:robots]` therefore lists task names for task-registered robots.
474
-
475
- ## Next Steps
476
-
477
- - [Memory Management](state-management.md) - Shared memory and reactive features
478
- - [Message Flow](message-flow.md) - How messages are processed within robots