robot_lab 0.2.6 → 0.2.7
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.
- checksums.yaml +4 -4
- data/README.md +124 -64
- data/docs/api/core/index.md +41 -15
- data/docs/api/core/memory.md +247 -29
- data/docs/api/core/network.md +255 -33
- data/docs/api/core/result.md +120 -32
- data/docs/api/core/robot.md +551 -61
- data/docs/api/core/state.md +87 -197
- data/docs/api/core/tool.md +165 -20
- data/docs/api/errors.md +110 -17
- data/docs/api/hooks.md +469 -0
- data/docs/api/index.md +80 -7
- data/docs/api/mcp/client.md +129 -35
- data/docs/api/mcp/index.md +164 -23
- data/docs/api/mcp/server.md +27 -3
- data/docs/api/mcp/transports.md +94 -22
- data/docs/api/messages/index.md +26 -3
- data/docs/api/messages/text-message.md +33 -11
- data/docs/api/messages/tool-call-message.md +27 -4
- data/docs/api/messages/tool-result-message.md +23 -4
- data/docs/api/messages/user-message.md +45 -8
- data/docs/api/skills.md +519 -0
- data/docs/api/streaming/context.md +28 -5
- data/docs/api/streaming/index.md +57 -11
- data/docs/api/support.md +846 -0
- data/docs/architecture/core-concepts.md +79 -31
- data/docs/architecture/index.md +86 -11
- data/docs/architecture/message-flow.md +66 -29
- data/docs/architecture/network-orchestration.md +145 -38
- data/docs/architecture/robot-execution.md +172 -90
- data/docs/architecture/state-management.md +31 -12
- data/docs/concepts.md +176 -21
- data/docs/examples/basic-chat.md +72 -19
- data/docs/examples/index.md +117 -31
- data/docs/examples/mcp-server.md +154 -45
- data/docs/examples/multi-robot-network.md +91 -21
- data/docs/examples/tool-usage.md +104 -37
- data/docs/getting-started/configuration.md +284 -97
- data/docs/getting-started/installation.md +53 -41
- data/docs/getting-started/quick-start.md +51 -6
- data/docs/guides/building-robots.md +258 -50
- data/docs/guides/creating-networks.md +214 -30
- data/docs/guides/hooks.md +141 -54
- data/docs/guides/knowledge.md +35 -4
- data/docs/guides/mcp-integration.md +211 -44
- data/docs/guides/memory.md +103 -12
- data/docs/guides/observability.md +95 -47
- data/docs/guides/streaming.md +184 -125
- data/docs/guides/using-tools.md +237 -17
- data/docs/index.md +36 -4
- data/examples/01_simple_robot.rb +2 -2
- data/examples/02_tools.rb +14 -4
- data/examples/03_network.rb +12 -7
- data/examples/04_mcp.rb +11 -4
- data/examples/05_streaming.rb +8 -5
- data/examples/06_prompt_templates.rb +13 -9
- data/examples/07_network_memory.rb +5 -5
- data/examples/08_llm_config.rb +20 -15
- data/examples/09_chaining.rb +4 -4
- data/examples/11_network_introspection.rb +4 -4
- data/examples/12_message_bus.rb +2 -2
- data/examples/13_spawn.rb +2 -2
- data/examples/14_rusty_circuit/README.md +1 -0
- data/examples/14_rusty_circuit/comic.rb +7 -3
- data/examples/14_rusty_circuit/display.rb +14 -0
- data/examples/14_rusty_circuit/heckler.rb +8 -6
- data/examples/14_rusty_circuit/open_mic.rb +17 -6
- data/examples/14_rusty_circuit/scout.rb +17 -10
- data/examples/15_memory_network_and_bus/editorial_pipeline.rb +14 -10
- data/examples/15_memory_network_and_bus/linux_writer.rb +2 -2
- data/examples/15_memory_network_and_bus/os_editor.rb +3 -1
- data/examples/15_memory_network_and_bus/os_writer.rb +4 -1
- data/examples/16_writers_room/writer.rb +22 -22
- data/examples/16_writers_room/writers_room.rb +2 -0
- data/examples/17_skills.rb +14 -13
- data/examples/18_rails/README.md +20 -1
- data/examples/18_rails/app/controllers/chat_controller.rb +5 -1
- data/examples/18_rails/app/jobs/robot_run_job.rb +11 -5
- data/examples/18_rails/app/robots/chat_robot.rb +11 -0
- data/examples/18_rails/config/initializers/robot_lab.rb +8 -0
- data/examples/19_token_tracking.rb +25 -9
- data/examples/20_circuit_breaker.rb +10 -7
- data/examples/21_learning_loop.rb +42 -16
- data/examples/22_context_compression.rb +23 -23
- data/examples/23_convergence.rb +24 -17
- data/examples/24_structured_delegation.rb +13 -8
- data/examples/25_history_search.rb +12 -8
- data/examples/27_incident_response/incident_response.rb +31 -13
- data/examples/28_mcp_discovery.rb +17 -13
- data/examples/29_ractor_tools.rb +4 -2
- data/examples/30_ractor_network.rb +22 -17
- data/examples/31_launch_assessment.rb +20 -9
- data/examples/32_newsletter_reader.rb +7 -2
- data/examples/33_stock_predictor.rb +34 -13
- data/examples/34_agentskills.rb +7 -3
- data/examples/35_hooks.rb +18 -8
- data/examples/README.md +199 -45
- data/examples/common.rb +79 -11
- data/examples/xyzzy.rb +8 -1
- data/lib/robot_lab/config.rb +10 -5
- data/lib/robot_lab/names.rb +402 -0
- data/lib/robot_lab/robot/agent_skill_matching.rb +1 -3
- data/lib/robot_lab/robot/bus_messaging.rb +16 -8
- data/lib/robot_lab/robot/template_rendering.rb +16 -3
- data/lib/robot_lab/robot.rb +23 -2
- data/lib/robot_lab/version.rb +1 -1
- data/lib/robot_lab.rb +21 -15
- data/mkdocs.yml +6 -1
- metadata +7 -2
|
@@ -46,6 +46,10 @@ result = network.run(message: "Analyze this quarterly data")
|
|
|
46
46
|
Tasks can have per-task configuration that is deep-merged with network run params:
|
|
47
47
|
|
|
48
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
|
+
|
|
49
53
|
network = RobotLab.create_network(name: "support") do
|
|
50
54
|
task :classifier, classifier_robot, depends_on: :none
|
|
51
55
|
task :billing, billing_robot,
|
|
@@ -59,6 +63,28 @@ network = RobotLab.create_network(name: "support") do
|
|
|
59
63
|
end
|
|
60
64
|
```
|
|
61
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
|
+
|
|
62
88
|
### Task Parameters
|
|
63
89
|
|
|
64
90
|
| Parameter | Type | Description |
|
|
@@ -69,7 +95,11 @@ end
|
|
|
69
95
|
| `mcp` | Symbol, Array | MCP server config (`:none`, `:inherit`, or array) |
|
|
70
96
|
| `tools` | Symbol, Array | Tools config (`:none`, `:inherit`, or array) |
|
|
71
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) |
|
|
72
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`.
|
|
73
103
|
|
|
74
104
|
## Execution Model
|
|
75
105
|
|
|
@@ -98,28 +128,33 @@ stateDiagram-v2
|
|
|
98
128
|
Each robot implements the SimpleFlow step interface via `call(result)`:
|
|
99
129
|
|
|
100
130
|
```ruby
|
|
101
|
-
# Inside Robot (simplified)
|
|
131
|
+
# Inside Robot (simplified -- timing and the rescue are elided)
|
|
102
132
|
def call(result)
|
|
103
133
|
run_context = extract_run_context(result)
|
|
104
134
|
message = run_context.delete(:message)
|
|
105
135
|
|
|
106
136
|
robot_result = run(message, **run_context)
|
|
137
|
+
robot_result.duration = ... # monotonic elapsed seconds
|
|
107
138
|
|
|
108
139
|
result
|
|
109
|
-
.with_context(@name.to_sym, robot_result)
|
|
140
|
+
.with_context(@name.to_sym, robot_result) # keyed by the ROBOT's name
|
|
110
141
|
.continue(robot_result)
|
|
111
142
|
end
|
|
112
143
|
```
|
|
113
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
|
+
|
|
114
147
|
### extract_run_context
|
|
115
148
|
|
|
116
149
|
The `extract_run_context` method pulls parameters from the SimpleFlow result:
|
|
117
150
|
|
|
118
|
-
-
|
|
119
|
-
-
|
|
120
|
-
-
|
|
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
|
|
121
155
|
- If it is a String, uses it directly as the message
|
|
122
|
-
- If it is a Hash, merges it
|
|
156
|
+
- If it is a Hash, merges it into the context
|
|
157
|
+
- Anything else is coerced with `to_s` and used as the message
|
|
123
158
|
|
|
124
159
|
## Task#call Interface
|
|
125
160
|
|
|
@@ -128,22 +163,36 @@ Each `Task` wraps a robot and enhances the SimpleFlow result before delegation:
|
|
|
128
163
|
```ruby
|
|
129
164
|
# Inside Task (simplified)
|
|
130
165
|
def call(result)
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
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
|
|
140
179
|
run_params[:memory] = @memory if @memory
|
|
141
180
|
|
|
142
|
-
|
|
143
|
-
|
|
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)
|
|
144
191
|
end
|
|
145
192
|
```
|
|
146
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
|
+
|
|
147
196
|
## SimpleFlow::Result
|
|
148
197
|
|
|
149
198
|
The result object flows through the pipeline:
|
|
@@ -151,30 +200,53 @@ The result object flows through the pipeline:
|
|
|
151
200
|
```ruby
|
|
152
201
|
result.value # Current task's output (RobotResult)
|
|
153
202
|
result.context # Accumulated context from all tasks
|
|
154
|
-
result.
|
|
155
|
-
result.continued? # Whether execution continues
|
|
203
|
+
result.continue? # Whether execution continues (the only status predicate)
|
|
156
204
|
```
|
|
157
205
|
|
|
158
206
|
### Result Methods
|
|
159
207
|
|
|
208
|
+
This is the complete public API of `SimpleFlow::Result` (simple_flow 0.4):
|
|
209
|
+
|
|
160
210
|
| Method | Description |
|
|
161
211
|
|--------|-------------|
|
|
212
|
+
| `value` | Current value flowing through the pipeline |
|
|
213
|
+
| `context` | Accumulated context hash |
|
|
162
214
|
| `continue(value)` | Continue to next tasks |
|
|
215
|
+
| `continue?` | Whether the pipeline is still continuing |
|
|
163
216
|
| `halt(value)` | Stop pipeline execution |
|
|
164
217
|
| `with_context(key, val)` | Add data to context |
|
|
218
|
+
| `with_error(key, message)` | Record an error (both arguments required) |
|
|
219
|
+
| `errors` | Recorded errors |
|
|
165
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.
|
|
166
224
|
|
|
167
225
|
### Context Structure
|
|
168
226
|
|
|
169
227
|
```ruby
|
|
170
228
|
{
|
|
171
|
-
run_params: { message: "...", customer_id: 123,
|
|
172
|
-
|
|
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
|
|
173
232
|
billing: RobotResult,
|
|
174
|
-
# ... other
|
|
233
|
+
# ... other robot results
|
|
175
234
|
}
|
|
176
235
|
```
|
|
177
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
|
+
|
|
178
250
|
## Optional Task Activation
|
|
179
251
|
|
|
180
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)`.
|
|
@@ -236,16 +308,37 @@ All robots in a network share the network's memory during execution. The network
|
|
|
236
308
|
|
|
237
309
|
```ruby
|
|
238
310
|
# Inside Network#run
|
|
239
|
-
def run(**run_context)
|
|
311
|
+
def run(message = nil, **run_context)
|
|
312
|
+
run_context[:message] = message unless message.nil? # Runnable protocol
|
|
313
|
+
|
|
240
314
|
run_context[:network_memory] = @memory
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
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
|
|
246
332
|
end
|
|
247
333
|
```
|
|
248
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
|
+
|
|
249
342
|
Robots use the shared memory for inter-robot communication:
|
|
250
343
|
|
|
251
344
|
```ruby
|
|
@@ -278,7 +371,7 @@ network.broadcast(event: :pause, reason: "rate limit hit")
|
|
|
278
371
|
network.broadcast(event: :phase_complete, phase: "analysis")
|
|
279
372
|
```
|
|
280
373
|
|
|
281
|
-
|
|
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]`.
|
|
282
375
|
|
|
283
376
|
## Parallel Execution
|
|
284
377
|
|
|
@@ -300,17 +393,23 @@ end
|
|
|
300
393
|
|
|
301
394
|
### Concurrency Modes
|
|
302
395
|
|
|
396
|
+
`create_network`'s `concurrency:` is passed straight to `SimpleFlow::Pipeline`:
|
|
397
|
+
|
|
303
398
|
| Mode | Description |
|
|
304
399
|
|------|-------------|
|
|
305
|
-
| `:auto` | SimpleFlow chooses best mode |
|
|
400
|
+
| `:auto` | SimpleFlow chooses best mode (default) |
|
|
306
401
|
| `:threads` | Use Ruby threads |
|
|
307
402
|
| `:async` | Use async/fiber |
|
|
308
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
|
+
|
|
309
408
|
## Data Flow
|
|
310
409
|
|
|
311
|
-
1. **Initial Value**: `network.run(**params)` creates an initial `SimpleFlow::Result` with the run context
|
|
312
|
-
2. **Run Params**: Stored in `result.context[:run_params]`
|
|
313
|
-
3. **Task Results**: Each
|
|
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`**
|
|
314
413
|
4. **Final Value**: Last task's output becomes `result.value`
|
|
315
414
|
|
|
316
415
|
```ruby
|
|
@@ -319,12 +418,15 @@ result = network.run(
|
|
|
319
418
|
customer_id: 123
|
|
320
419
|
)
|
|
321
420
|
|
|
322
|
-
result.context[:run_params] #=> { message: "...", customer_id: 123,
|
|
323
|
-
|
|
324
|
-
result.context[:
|
|
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"
|
|
325
425
|
result.value #=> Final RobotResult
|
|
326
426
|
```
|
|
327
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
|
+
|
|
328
430
|
## Visualization
|
|
329
431
|
|
|
330
432
|
Networks provide visualization methods via the underlying SimpleFlow pipeline:
|
|
@@ -346,12 +448,13 @@ puts network.execution_plan
|
|
|
346
448
|
## Network Inspection
|
|
347
449
|
|
|
348
450
|
```ruby
|
|
349
|
-
# Get a robot by
|
|
451
|
+
# Get a robot by its registration key
|
|
350
452
|
network.robot(:classifier) #=> Robot
|
|
351
453
|
network[:classifier] #=> Robot (alias)
|
|
352
454
|
|
|
353
455
|
# List all robots
|
|
354
456
|
network.available_robots #=> [Robot, Robot, ...]
|
|
457
|
+
network.crew #=> same array, via the Runnable protocol
|
|
355
458
|
|
|
356
459
|
# Add a robot without a task
|
|
357
460
|
network.add_robot(extra_robot)
|
|
@@ -362,9 +465,13 @@ network.remove_robot(:extra_robot)
|
|
|
362
465
|
|
|
363
466
|
# Convert to hash
|
|
364
467
|
network.to_h
|
|
365
|
-
#=> { name: "support", robots: ["classifier", "billing"],
|
|
468
|
+
#=> { name: "support", robots: ["classifier", "billing"],
|
|
469
|
+
# tasks: ["classifier", "billing"], optional_tasks: [],
|
|
470
|
+
# config: { ... } } # :config present only when the network config is non-empty
|
|
366
471
|
```
|
|
367
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
|
+
|
|
368
475
|
## Next Steps
|
|
369
476
|
|
|
370
477
|
- [Memory Management](state-management.md) - Shared memory and reactive features
|