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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +12 -0
- data/README.md +1 -1
- data/lib/robot_lab/mcp/transports/streamable_http.rb +1 -1
- data/lib/robot_lab/version.rb +1 -1
- metadata +3 -253
- data/.architecture/AGENTS.md +0 -32
- data/.architecture/config.yml +0 -8
- data/.architecture/members.yml +0 -60
- data/.architecture/reviews/feature-free-will.md +0 -490
- data/.architecture/reviews/overall-codebase.md +0 -427
- data/.claude/settings.local.json +0 -57
- data/.codex/config.toml +0 -2
- data/.envrc +0 -6
- data/.github/workflows/deploy-github-pages.yml +0 -52
- data/.irbrc +0 -6
- data/.loki +0 -65
- data/.quality/reek_baseline.txt +0 -43
- data/.rubocop.yml +0 -10
- data/Archspec.rb +0 -44
- data/CLAUDE.md +0 -141
- data/COMMITS.md +0 -196
- data/Rakefile +0 -193
- data/_typos.toml +0 -21
- data/agent2agent_review.md +0 -192
- data/agentf_improvements.md +0 -253
- data/agents.md +0 -14
- data/docs/api/core/index.md +0 -147
- data/docs/api/core/memory.md +0 -578
- data/docs/api/core/network.md +0 -553
- data/docs/api/core/result.md +0 -211
- data/docs/api/core/robot.md +0 -1667
- data/docs/api/core/state.md +0 -145
- data/docs/api/core/tool.md +0 -520
- data/docs/api/errors.md +0 -323
- data/docs/api/hooks.md +0 -469
- data/docs/api/index.md +0 -137
- data/docs/api/mcp/client.md +0 -387
- data/docs/api/mcp/index.md +0 -267
- data/docs/api/mcp/server.md +0 -252
- data/docs/api/mcp/transports.md +0 -339
- data/docs/api/messages/index.md +0 -126
- data/docs/api/messages/text-message.md +0 -170
- data/docs/api/messages/tool-call-message.md +0 -206
- data/docs/api/messages/tool-result-message.md +0 -242
- data/docs/api/messages/user-message.md +0 -232
- data/docs/api/skills.md +0 -423
- data/docs/api/streaming/context.md +0 -280
- data/docs/api/streaming/events.md +0 -185
- data/docs/api/streaming/index.md +0 -156
- data/docs/api/support.md +0 -846
- data/docs/architecture/core-concepts.md +0 -575
- data/docs/architecture/index.md +0 -252
- data/docs/architecture/message-flow.md +0 -366
- data/docs/architecture/network-orchestration.md +0 -478
- data/docs/architecture/robot-execution.md +0 -396
- data/docs/architecture/state-management.md +0 -410
- data/docs/assets/css/custom.css +0 -56
- data/docs/assets/images/robot_lab.jpg +0 -0
- data/docs/concepts.md +0 -641
- data/docs/examples/basic-chat.md +0 -258
- data/docs/examples/index.md +0 -425
- data/docs/examples/mcp-server.md +0 -403
- data/docs/examples/multi-robot-network.md +0 -476
- data/docs/examples/tool-usage.md +0 -468
- data/docs/getting-started/configuration.md +0 -645
- data/docs/getting-started/index.md +0 -56
- data/docs/getting-started/installation.md +0 -197
- data/docs/getting-started/quick-start.md +0 -293
- data/docs/guides/building-robots.md +0 -1187
- data/docs/guides/creating-networks.md +0 -736
- data/docs/guides/hooks.md +0 -1206
- data/docs/guides/index.md +0 -78
- data/docs/guides/knowledge.md +0 -219
- data/docs/guides/mcp-integration.md +0 -740
- data/docs/guides/memory.md +0 -458
- data/docs/guides/observability.md +0 -733
- data/docs/guides/streaming.md +0 -323
- data/docs/guides/using-tools.md +0 -780
- data/docs/index.md +0 -280
- data/docs/superpowers/plans/2026-05-06-agentskills.md +0 -1303
- data/docs/superpowers/specs/2026-05-06-agentskills-design.md +0 -247
- data/examples/.envrc +0 -3
- data/examples/01_simple_robot.rb +0 -31
- data/examples/02_tools.rb +0 -109
- data/examples/03_network.rb +0 -118
- data/examples/04_mcp.rb +0 -217
- data/examples/05_streaming.rb +0 -137
- data/examples/06_prompt_templates.rb +0 -325
- data/examples/07_network_memory.rb +0 -313
- data/examples/08_llm_config.rb +0 -162
- data/examples/09_chaining.rb +0 -251
- data/examples/10_memory.rb +0 -315
- data/examples/11_network_introspection.rb +0 -239
- data/examples/12_message_bus.rb +0 -67
- data/examples/13_spawn.rb +0 -85
- data/examples/14_rusty_circuit/.envrc +0 -1
- data/examples/14_rusty_circuit/.gitignore +0 -1
- data/examples/14_rusty_circuit/README.md +0 -1
- data/examples/14_rusty_circuit/comic.rb +0 -149
- data/examples/14_rusty_circuit/display.rb +0 -217
- data/examples/14_rusty_circuit/heckler.rb +0 -65
- data/examples/14_rusty_circuit/open_mic.rb +0 -132
- data/examples/14_rusty_circuit/prompts/open_mic_comic.md +0 -20
- data/examples/14_rusty_circuit/prompts/open_mic_heckler.md +0 -23
- data/examples/14_rusty_circuit/prompts/open_mic_scout.md +0 -20
- data/examples/14_rusty_circuit/scout.rb +0 -165
- data/examples/14_rusty_circuit/show.log +0 -234
- data/examples/15_memory_network_and_bus/.envrc +0 -1
- data/examples/15_memory_network_and_bus/README.md +0 -66
- data/examples/15_memory_network_and_bus/editor_in_chief.rb +0 -24
- data/examples/15_memory_network_and_bus/editorial_pipeline.rb +0 -213
- data/examples/15_memory_network_and_bus/linux_writer.rb +0 -80
- data/examples/15_memory_network_and_bus/os_editor.rb +0 -48
- data/examples/15_memory_network_and_bus/os_writer.rb +0 -49
- data/examples/15_memory_network_and_bus/output/combined_article.md +0 -11
- data/examples/15_memory_network_and_bus/output/final_article.md +0 -8
- data/examples/15_memory_network_and_bus/output/linux_draft.md +0 -7
- data/examples/15_memory_network_and_bus/output/mac_draft.md +0 -7
- data/examples/15_memory_network_and_bus/output/memory.json +0 -13
- data/examples/15_memory_network_and_bus/output/revision_1.md +0 -29
- data/examples/15_memory_network_and_bus/output/revision_2.md +0 -50
- data/examples/15_memory_network_and_bus/output/revision_3.md +0 -8
- data/examples/15_memory_network_and_bus/output/windows_draft.md +0 -7
- data/examples/15_memory_network_and_bus/prompts/os_advocate.md +0 -13
- data/examples/15_memory_network_and_bus/prompts/os_chief.md +0 -13
- data/examples/15_memory_network_and_bus/prompts/os_editor.md +0 -13
- data/examples/16_writers_room/.envrc +0 -1
- data/examples/16_writers_room/display.rb +0 -158
- data/examples/16_writers_room/output/.gitignore +0 -4
- data/examples/16_writers_room/output/README.md +0 -69
- data/examples/16_writers_room/output/opus_001.md +0 -263
- data/examples/16_writers_room/output/opus_001_notes.log +0 -470
- data/examples/16_writers_room/output/opus_002.md +0 -245
- data/examples/16_writers_room/output/opus_002_notes.log +0 -546
- data/examples/16_writers_room/output/opus_002_screenplay.md +0 -7989
- data/examples/16_writers_room/output/opus_002_screenplay_notes.md +0 -993
- data/examples/16_writers_room/prompts/screenplay_writer.md +0 -66
- data/examples/16_writers_room/prompts/writer.md +0 -37
- data/examples/16_writers_room/room.rb +0 -186
- data/examples/16_writers_room/tools.rb +0 -173
- data/examples/16_writers_room/writer.rb +0 -121
- data/examples/16_writers_room/writers_room.rb +0 -256
- data/examples/17_skills.rb +0 -234
- data/examples/18_rails/.envrc +0 -3
- data/examples/18_rails/.gitignore +0 -5
- data/examples/18_rails/Gemfile +0 -11
- data/examples/18_rails/README.md +0 -67
- data/examples/18_rails/Rakefile +0 -4
- data/examples/18_rails/app/controllers/application_controller.rb +0 -4
- data/examples/18_rails/app/controllers/chat_controller.rb +0 -50
- data/examples/18_rails/app/jobs/application_job.rb +0 -4
- data/examples/18_rails/app/jobs/robot_run_job.rb +0 -25
- data/examples/18_rails/app/models/application_record.rb +0 -5
- data/examples/18_rails/app/models/robot_lab_result.rb +0 -36
- data/examples/18_rails/app/models/robot_lab_thread.rb +0 -23
- data/examples/18_rails/app/robots/chat_robot.rb +0 -25
- data/examples/18_rails/app/tools/time_tool.rb +0 -9
- data/examples/18_rails/app/views/chat/_user_message.html.erb +0 -1
- data/examples/18_rails/app/views/chat/index.html.erb +0 -67
- data/examples/18_rails/app/views/layouts/application.html.erb +0 -49
- data/examples/18_rails/bin/dev +0 -7
- data/examples/18_rails/bin/rails +0 -6
- data/examples/18_rails/bin/setup +0 -15
- data/examples/18_rails/config/application.rb +0 -33
- data/examples/18_rails/config/cable.yml +0 -2
- data/examples/18_rails/config/database.yml +0 -5
- data/examples/18_rails/config/environment.rb +0 -4
- data/examples/18_rails/config/initializers/robot_lab.rb +0 -11
- data/examples/18_rails/config/routes.rb +0 -6
- data/examples/18_rails/config.ru +0 -4
- data/examples/18_rails/db/migrate/001_create_robot_lab_tables.rb +0 -32
- data/examples/19_token_tracking.rb +0 -138
- data/examples/20_circuit_breaker.rb +0 -147
- data/examples/21_learning_loop.rb +0 -181
- data/examples/22_context_compression.rb +0 -172
- data/examples/23_convergence.rb +0 -133
- data/examples/24_structured_delegation.rb +0 -151
- data/examples/25_history_search/conversation.jsonl +0 -30
- data/examples/25_history_search.rb +0 -133
- data/examples/26_document_store/api_versioning_adr.md +0 -52
- data/examples/26_document_store/incident_postmortem.md +0 -46
- data/examples/26_document_store/postgres_runbook.md +0 -49
- data/examples/26_document_store/redis_caching_guide.md +0 -48
- data/examples/26_document_store/sidekiq_guide.md +0 -51
- data/examples/26_document_store.rb +0 -140
- data/examples/27_incident_response/README.md +0 -65
- data/examples/27_incident_response/incident_response.rb +0 -261
- data/examples/28_mcp_discovery.rb +0 -113
- data/examples/29_ractor_tools.rb +0 -240
- data/examples/30_ractor_network.rb +0 -252
- data/examples/31_launch_assessment.rb +0 -246
- data/examples/32_newsletter_reader.rb +0 -193
- data/examples/33_stock_generator.rb +0 -80
- data/examples/33_stock_predictor.rb +0 -327
- data/examples/34_agentskills.rb +0 -76
- data/examples/35_hooks.rb +0 -266
- data/examples/README.md +0 -550
- data/examples/common.rb +0 -180
- data/examples/prompts/assistant.md +0 -5
- data/examples/prompts/audit_trail.md +0 -8
- data/examples/prompts/billing.md +0 -10
- data/examples/prompts/classifier.md +0 -7
- data/examples/prompts/comedian.md +0 -6
- data/examples/prompts/comedy_critic.md +0 -10
- data/examples/prompts/configurable.md +0 -9
- data/examples/prompts/dispatcher.md +0 -12
- data/examples/prompts/entity_extractor.md +0 -14
- data/examples/prompts/escalation.md +0 -42
- data/examples/prompts/frontmatter_mcp_test.md +0 -9
- data/examples/prompts/frontmatter_named_test.md +0 -5
- data/examples/prompts/frontmatter_tools_test.md +0 -6
- data/examples/prompts/general.md +0 -7
- data/examples/prompts/github_assistant.md +0 -14
- data/examples/prompts/helper.md +0 -4
- data/examples/prompts/incident_responder.md +0 -18
- data/examples/prompts/keyword_extractor.md +0 -11
- data/examples/prompts/llm_config_demo.md +0 -20
- data/examples/prompts/order_support.md +0 -35
- data/examples/prompts/os_advocate.md +0 -13
- data/examples/prompts/os_chief.md +0 -13
- data/examples/prompts/os_editor.md +0 -13
- data/examples/prompts/parameterized_main_test.md +0 -6
- data/examples/prompts/pii_redactor.md +0 -7
- data/examples/prompts/product_support.md +0 -37
- data/examples/prompts/runbook_protocol.md +0 -12
- data/examples/prompts/sentiment_analyzer.md +0 -12
- data/examples/prompts/skill_a_test.md +0 -4
- data/examples/prompts/skill_b_test.md +0 -4
- data/examples/prompts/skill_config_test.md +0 -5
- data/examples/prompts/skill_cycle_a_test.md +0 -6
- data/examples/prompts/skill_cycle_b_test.md +0 -6
- data/examples/prompts/skill_description_test.md +0 -4
- data/examples/prompts/skill_leaf_test.md +0 -4
- data/examples/prompts/skill_nested_test.md +0 -6
- data/examples/prompts/skill_refs_main_test.md +0 -6
- data/examples/prompts/skill_self_ref_test.md +0 -6
- data/examples/prompts/skill_with_mcp_test.md +0 -9
- data/examples/prompts/skill_with_params_test.md +0 -6
- data/examples/prompts/skill_with_robot_name_test.md +0 -5
- data/examples/prompts/skill_with_tools_test.md +0 -6
- data/examples/prompts/sre_compliance.md +0 -10
- data/examples/prompts/structured_output.md +0 -7
- data/examples/prompts/synthesizer.md +0 -17
- data/examples/prompts/technical.md +0 -10
- data/examples/prompts/template_with_skills_test.md +0 -6
- data/examples/prompts/triage.md +0 -22
- data/examples/ruboruby.md +0 -423
- data/examples/run_all.rb +0 -60
- data/examples/xyzzy.rb +0 -97
- data/logfile +0 -8
- data/mkdocs.yml +0 -209
- data/robot_concurrency.md +0 -38
- data/simple_acp_review.md +0 -298
- data/temp.md +0 -6
- data/tool_manifest_plan.md +0 -155
data/docs/api/core/network.md
DELETED
|
@@ -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)
|