robot_lab 0.2.1 → 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/.envrc +1 -0
- data/.loki +60 -0
- data/.quality/reek_baseline.txt +43 -0
- data/.rubocop.yml +5 -167
- data/CHANGELOG.md +54 -0
- data/README.md +185 -64
- data/Rakefile +28 -12
- data/docs/api/core/index.md +41 -14
- data/docs/api/core/memory.md +247 -29
- data/docs/api/core/network.md +285 -33
- data/docs/api/core/result.md +120 -32
- data/docs/api/core/robot.md +636 -60
- data/docs/api/core/state.md +87 -197
- data/docs/api/core/tool.md +165 -20
- data/docs/api/errors.md +152 -14
- data/docs/api/hooks.md +469 -0
- data/docs/api/index.md +83 -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 +118 -30
- data/docs/architecture/index.md +86 -11
- data/docs/architecture/message-flow.md +66 -29
- data/docs/architecture/network-orchestration.md +153 -38
- data/docs/architecture/robot-execution.md +173 -91
- 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 +299 -93
- data/docs/getting-started/installation.md +53 -41
- data/docs/getting-started/quick-start.md +51 -6
- data/docs/guides/building-robots.md +296 -50
- data/docs/guides/creating-networks.md +220 -31
- data/docs/guides/hooks.md +1084 -0
- data/docs/guides/index.md +5 -0
- data/docs/guides/knowledge.md +37 -6
- data/docs/guides/mcp-integration.md +211 -44
- data/docs/guides/memory.md +103 -12
- data/docs/guides/observability.md +166 -51
- data/docs/guides/streaming.md +184 -125
- data/docs/guides/using-tools.md +300 -11
- data/docs/index.md +37 -5
- data/examples/01_simple_robot.rb +2 -2
- data/examples/02_tools.rb +14 -4
- data/examples/03_network.rb +23 -9
- 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 +266 -0
- data/examples/README.md +203 -42
- data/examples/common.rb +79 -7
- data/examples/xyzzy.rb +97 -0
- data/lib/robot_lab/agent_skill.rb +5 -4
- data/lib/robot_lab/budget/ledger.rb +98 -0
- data/lib/robot_lab/capabilities.rb +84 -0
- data/lib/robot_lab/config/defaults.yml +10 -0
- data/lib/robot_lab/config.rb +14 -9
- data/lib/robot_lab/error.rb +34 -4
- data/lib/robot_lab/errors.rb +45 -0
- data/lib/robot_lab/hook.rb +79 -0
- data/lib/robot_lab/hook_context.rb +194 -0
- data/lib/robot_lab/hook_registry.rb +55 -0
- data/lib/robot_lab/hooks.rb +87 -0
- data/lib/robot_lab/mcp/connection_poller.rb +2 -2
- data/lib/robot_lab/names.rb +402 -0
- data/lib/robot_lab/narrator.rb +87 -0
- data/lib/robot_lab/network.rb +77 -20
- data/lib/robot_lab/robot/agent_skill_matching.rb +1 -3
- data/lib/robot_lab/robot/budget.rb +89 -0
- data/lib/robot_lab/robot/bus_messaging.rb +90 -18
- data/lib/robot_lab/robot/hooking.rb +56 -0
- data/lib/robot_lab/robot/mcp_management.rb +8 -2
- data/lib/robot_lab/robot/template_rendering.rb +18 -5
- data/lib/robot_lab/robot.rb +233 -89
- data/lib/robot_lab/run_config.rb +24 -5
- data/lib/robot_lab/runnable.rb +51 -0
- data/lib/robot_lab/sandbox/null.rb +13 -0
- data/lib/robot_lab/sandbox/seatbelt.rb +104 -0
- data/lib/robot_lab/sandbox.rb +52 -0
- data/lib/robot_lab/script_tool.rb +60 -2
- data/lib/robot_lab/task.rb +26 -20
- data/lib/robot_lab/tool.rb +52 -11
- data/lib/robot_lab/version.rb +1 -1
- data/lib/robot_lab.rb +70 -15
- data/mkdocs.yml +7 -1
- metadata +41 -99
- data/examples/temp.md +0 -51
- data/site/404.html +0 -2300
- data/site/api/core/index.html +0 -2706
- data/site/api/core/memory/index.html +0 -3793
- data/site/api/core/network/index.html +0 -3500
- data/site/api/core/robot/index.html +0 -4566
- data/site/api/core/state/index.html +0 -3390
- data/site/api/core/tool/index.html +0 -3843
- data/site/api/index.html +0 -2635
- data/site/api/mcp/client/index.html +0 -3435
- data/site/api/mcp/index.html +0 -2783
- data/site/api/mcp/server/index.html +0 -3252
- data/site/api/mcp/transports/index.html +0 -3352
- data/site/api/messages/index.html +0 -2641
- data/site/api/messages/text-message/index.html +0 -3087
- data/site/api/messages/tool-call-message/index.html +0 -3159
- data/site/api/messages/tool-result-message/index.html +0 -3252
- data/site/api/messages/user-message/index.html +0 -3212
- data/site/api/streaming/context/index.html +0 -3282
- data/site/api/streaming/events/index.html +0 -3347
- data/site/api/streaming/index.html +0 -2738
- data/site/architecture/core-concepts/index.html +0 -3757
- data/site/architecture/index.html +0 -2797
- data/site/architecture/message-flow/index.html +0 -3238
- data/site/architecture/network-orchestration/index.html +0 -3433
- data/site/architecture/robot-execution/index.html +0 -3140
- data/site/architecture/state-management/index.html +0 -3498
- data/site/assets/css/custom.css +0 -56
- data/site/assets/images/favicon.png +0 -0
- data/site/assets/images/robot_lab.jpg +0 -0
- data/site/assets/javascripts/bundle.79ae519e.min.js +0 -16
- data/site/assets/javascripts/bundle.79ae519e.min.js.map +0 -7
- data/site/assets/javascripts/lunr/min/lunr.ar.min.js +0 -1
- data/site/assets/javascripts/lunr/min/lunr.da.min.js +0 -18
- data/site/assets/javascripts/lunr/min/lunr.de.min.js +0 -18
- data/site/assets/javascripts/lunr/min/lunr.du.min.js +0 -18
- data/site/assets/javascripts/lunr/min/lunr.el.min.js +0 -1
- data/site/assets/javascripts/lunr/min/lunr.es.min.js +0 -18
- data/site/assets/javascripts/lunr/min/lunr.fi.min.js +0 -18
- data/site/assets/javascripts/lunr/min/lunr.fr.min.js +0 -18
- data/site/assets/javascripts/lunr/min/lunr.he.min.js +0 -1
- data/site/assets/javascripts/lunr/min/lunr.hi.min.js +0 -1
- data/site/assets/javascripts/lunr/min/lunr.hu.min.js +0 -18
- data/site/assets/javascripts/lunr/min/lunr.hy.min.js +0 -1
- data/site/assets/javascripts/lunr/min/lunr.it.min.js +0 -18
- data/site/assets/javascripts/lunr/min/lunr.ja.min.js +0 -1
- data/site/assets/javascripts/lunr/min/lunr.jp.min.js +0 -1
- data/site/assets/javascripts/lunr/min/lunr.kn.min.js +0 -1
- data/site/assets/javascripts/lunr/min/lunr.ko.min.js +0 -1
- data/site/assets/javascripts/lunr/min/lunr.multi.min.js +0 -1
- data/site/assets/javascripts/lunr/min/lunr.nl.min.js +0 -18
- data/site/assets/javascripts/lunr/min/lunr.no.min.js +0 -18
- data/site/assets/javascripts/lunr/min/lunr.pt.min.js +0 -18
- data/site/assets/javascripts/lunr/min/lunr.ro.min.js +0 -18
- data/site/assets/javascripts/lunr/min/lunr.ru.min.js +0 -18
- data/site/assets/javascripts/lunr/min/lunr.sa.min.js +0 -1
- data/site/assets/javascripts/lunr/min/lunr.stemmer.support.min.js +0 -1
- data/site/assets/javascripts/lunr/min/lunr.sv.min.js +0 -18
- data/site/assets/javascripts/lunr/min/lunr.ta.min.js +0 -1
- data/site/assets/javascripts/lunr/min/lunr.te.min.js +0 -1
- data/site/assets/javascripts/lunr/min/lunr.th.min.js +0 -1
- data/site/assets/javascripts/lunr/min/lunr.tr.min.js +0 -18
- data/site/assets/javascripts/lunr/min/lunr.vi.min.js +0 -1
- data/site/assets/javascripts/lunr/min/lunr.zh.min.js +0 -1
- data/site/assets/javascripts/lunr/tinyseg.js +0 -206
- data/site/assets/javascripts/lunr/wordcut.js +0 -6708
- data/site/assets/javascripts/workers/search.2c215733.min.js +0 -42
- data/site/assets/javascripts/workers/search.2c215733.min.js.map +0 -7
- data/site/assets/stylesheets/main.484c7ddc.min.css +0 -1
- data/site/assets/stylesheets/main.484c7ddc.min.css.map +0 -1
- data/site/assets/stylesheets/palette.ab4e12ef.min.css +0 -1
- data/site/assets/stylesheets/palette.ab4e12ef.min.css.map +0 -1
- data/site/concepts/index.html +0 -3455
- data/site/examples/basic-chat/index.html +0 -2880
- data/site/examples/index.html +0 -2907
- data/site/examples/mcp-server/index.html +0 -3018
- data/site/examples/multi-robot-network/index.html +0 -3131
- data/site/examples/rails-application/index.html +0 -3329
- data/site/examples/tool-usage/index.html +0 -3085
- data/site/getting-started/configuration/index.html +0 -3745
- data/site/getting-started/index.html +0 -2572
- data/site/getting-started/installation/index.html +0 -2981
- data/site/getting-started/quick-start/index.html +0 -2942
- data/site/guides/building-robots/index.html +0 -4290
- data/site/guides/creating-networks/index.html +0 -3858
- data/site/guides/index.html +0 -2586
- data/site/guides/mcp-integration/index.html +0 -3581
- data/site/guides/memory/index.html +0 -3586
- data/site/guides/rails-integration/index.html +0 -4019
- data/site/guides/streaming/index.html +0 -3157
- data/site/guides/using-tools/index.html +0 -3802
- data/site/index.html +0 -2671
- data/site/search/search_index.json +0 -1
- data/site/sitemap.xml +0 -183
- data/site/sitemap.xml.gz +0 -0
- data/site/tags.json +0 -1
data/docs/api/core/network.md
CHANGED
|
@@ -11,6 +11,17 @@ network = RobotLab.create_network(name: "support", config: config) do
|
|
|
11
11
|
end
|
|
12
12
|
```
|
|
13
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
|
+
|
|
14
25
|
## Attributes
|
|
15
26
|
|
|
16
27
|
### name
|
|
@@ -27,7 +38,37 @@ Network identifier for logging and debugging.
|
|
|
27
38
|
network.robots # => Hash<String, Robot>
|
|
28
39
|
```
|
|
29
40
|
|
|
30
|
-
|
|
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:)`.
|
|
31
72
|
|
|
32
73
|
### config
|
|
33
74
|
|
|
@@ -35,7 +76,35 @@ Hash of robots keyed by name.
|
|
|
35
76
|
network.config # => RunConfig
|
|
36
77
|
```
|
|
37
78
|
|
|
38
|
-
Shared operational defaults
|
|
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`.
|
|
39
108
|
|
|
40
109
|
### pipeline
|
|
41
110
|
|
|
@@ -56,6 +125,9 @@ result = network.run(
|
|
|
56
125
|
**context
|
|
57
126
|
)
|
|
58
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)
|
|
59
131
|
```
|
|
60
132
|
|
|
61
133
|
Execute the network pipeline.
|
|
@@ -64,15 +136,28 @@ Execute the network pipeline.
|
|
|
64
136
|
|
|
65
137
|
| Name | Type | Description |
|
|
66
138
|
|------|------|-------------|
|
|
67
|
-
| `message` | `String` | The input message |
|
|
139
|
+
| `message` | `String`, `nil` | The input message — as the `message:` keyword, or positionally (folded into `message:` when given) |
|
|
68
140
|
| `**context` | `Hash` | Additional context passed to all robots |
|
|
69
141
|
|
|
70
|
-
|
|
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.
|
|
71
154
|
|
|
72
155
|
### task
|
|
73
156
|
|
|
74
157
|
```ruby
|
|
75
|
-
network.task(name, robot,
|
|
158
|
+
network.task(name, robot,
|
|
159
|
+
context: {}, mcp: :none, tools: :none, memory: nil,
|
|
160
|
+
config: nil, depends_on: :none, poller_group: :default)
|
|
76
161
|
# => self
|
|
77
162
|
```
|
|
78
163
|
|
|
@@ -80,16 +165,17 @@ Add a task to the pipeline with optional per-task configuration.
|
|
|
80
165
|
|
|
81
166
|
**Parameters:**
|
|
82
167
|
|
|
83
|
-
| Name | Type | Description |
|
|
84
|
-
|
|
85
|
-
| `name` | `Symbol` | Task identifier |
|
|
86
|
-
| `robot` | `Robot` | Robot instance to execute |
|
|
87
|
-
| `context` | `Hash` | Task-specific context (deep-merged with run params) |
|
|
88
|
-
| `mcp` |
|
|
89
|
-
| `tools` |
|
|
90
|
-
| `memory` | `Memory`, `nil` | Task-specific memory |
|
|
91
|
-
| `config` | `RunConfig`, `nil` | Per-task config
|
|
92
|
-
| `depends_on` | `:none`, `Array<Symbol>`, `:optional` | Task dependencies |
|
|
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 |
|
|
93
179
|
|
|
94
180
|
**Dependency Types:**
|
|
95
181
|
|
|
@@ -97,7 +183,106 @@ Add a task to the pipeline with optional per-task configuration.
|
|
|
97
183
|
|-------|-------------|
|
|
98
184
|
| `:none` | No dependencies, runs first |
|
|
99
185
|
| `[:task1, :task2]` | Waits for listed tasks to complete |
|
|
100
|
-
| `:optional` | Only runs when explicitly activated |
|
|
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`).
|
|
101
286
|
|
|
102
287
|
### add_robot
|
|
103
288
|
|
|
@@ -108,6 +293,15 @@ network.add_robot(robot)
|
|
|
108
293
|
|
|
109
294
|
Add a robot without creating a pipeline task. Useful for robots referenced by other tasks.
|
|
110
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
|
+
|
|
111
305
|
### robot / []
|
|
112
306
|
|
|
113
307
|
```ruby
|
|
@@ -160,34 +354,58 @@ Hash representation of network configuration.
|
|
|
160
354
|
```ruby
|
|
161
355
|
{
|
|
162
356
|
name: "support",
|
|
163
|
-
robots: ["classifier", "billing", "technical"],
|
|
164
|
-
tasks: ["classifier", "billing", "technical"],
|
|
357
|
+
robots: ["classifier", "billing", "technical"], # keys of network.robots
|
|
358
|
+
tasks: ["classifier", "billing", "technical"], # task names only
|
|
165
359
|
optional_tasks: [:billing, :technical],
|
|
166
|
-
config: { model: "claude-sonnet-4", temperature: 0.7 }
|
|
360
|
+
config: { model: "claude-sonnet-4", temperature: 0.7 }
|
|
167
361
|
}
|
|
168
362
|
```
|
|
169
363
|
|
|
170
|
-
|
|
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.
|
|
171
369
|
|
|
172
|
-
|
|
370
|
+
## SimpleFlow::Result
|
|
173
371
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
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
|
+
```
|
|
182
396
|
|
|
183
397
|
### Context Structure
|
|
184
398
|
|
|
185
399
|
```ruby
|
|
186
400
|
result.context[:run_params] # Original run parameters
|
|
187
|
-
result.context[:classifier] #
|
|
188
|
-
result.context[:billing] #
|
|
401
|
+
result.context[:classifier] # RobotResult from the robot NAMED "classifier"
|
|
402
|
+
result.context[:billing] # RobotResult from the robot NAMED "billing"
|
|
189
403
|
```
|
|
190
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
|
+
|
|
191
409
|
## Builder DSL
|
|
192
410
|
|
|
193
411
|
### task
|
|
@@ -210,11 +428,17 @@ network = RobotLab.create_network(name: "support") do
|
|
|
210
428
|
depends_on: :optional
|
|
211
429
|
task :technical, technical_robot,
|
|
212
430
|
context: { department: "technical" },
|
|
213
|
-
|
|
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],
|
|
214
435
|
depends_on: :optional
|
|
215
436
|
end
|
|
216
437
|
```
|
|
217
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
|
+
|
|
218
442
|
## Examples
|
|
219
443
|
|
|
220
444
|
### Sequential Pipeline
|
|
@@ -247,10 +471,16 @@ end
|
|
|
247
471
|
|
|
248
472
|
### Conditional Routing
|
|
249
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
|
+
|
|
250
478
|
```ruby
|
|
251
479
|
class ClassifierRobot < RobotLab::Robot
|
|
252
480
|
def call(result)
|
|
253
|
-
|
|
481
|
+
run_context = extract_run_context(result)
|
|
482
|
+
message = run_context.delete(:message) # must be positional
|
|
483
|
+
robot_result = run(message, **run_context)
|
|
254
484
|
|
|
255
485
|
new_result = result
|
|
256
486
|
.with_context(@name.to_sym, robot_result)
|
|
@@ -294,6 +524,28 @@ elsif result.context[:technical]
|
|
|
294
524
|
end
|
|
295
525
|
```
|
|
296
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
|
+
|
|
297
549
|
## See Also
|
|
298
550
|
|
|
299
551
|
- [Creating Networks Guide](../../guides/creating-networks.md)
|
data/docs/api/core/result.md
CHANGED
|
@@ -8,12 +8,34 @@
|
|
|
8
8
|
result = robot.run("What is the capital of France?")
|
|
9
9
|
|
|
10
10
|
result.reply # => "The capital of France is Paris."
|
|
11
|
-
result.last_text_content # => alias for
|
|
12
|
-
result.output # =>
|
|
13
|
-
result.tool_calls # =>
|
|
11
|
+
result.last_text_content # => reply is an alias for this
|
|
12
|
+
result.output # => [TextMessage] — see below
|
|
13
|
+
result.tool_calls # => [] in practice — see below
|
|
14
14
|
```
|
|
15
15
|
|
|
16
|
-
`
|
|
16
|
+
`last_text_content` returns the content of the last text message in `output`;
|
|
17
|
+
`reply` is an alias for it. This is the string you want for the vast majority of
|
|
18
|
+
use cases.
|
|
19
|
+
|
|
20
|
+
!!! note "`output` is not the full turn"
|
|
21
|
+
`output` is built as `[TextMessage.new(role: "assistant", content: text)]`
|
|
22
|
+
from the **final response text only** — a one-element array, or an empty
|
|
23
|
+
array when there is no text. It never contains the user message, the tool
|
|
24
|
+
calls, or the intermediate assistant turns. Read `robot.messages` for the
|
|
25
|
+
real conversation.
|
|
26
|
+
|
|
27
|
+
The text is resolved in this order: `response.content`; then
|
|
28
|
+
`response.thinking.text` (for models that route all output through reasoning
|
|
29
|
+
content, e.g. `qwen3` on Ollama); then the most recent assistant text from
|
|
30
|
+
*the current turn only* in the chat history.
|
|
31
|
+
|
|
32
|
+
!!! note "`tool_calls` is effectively always empty"
|
|
33
|
+
It is populated from the **final** assistant message, which no longer carries
|
|
34
|
+
tool calls once ruby_llm's tool loop has completed. Consequently
|
|
35
|
+
`has_tool_calls?` is also normally `false`. To observe tool activity, use the
|
|
36
|
+
`on_tool_call:` / `on_tool_result:` callbacks or the `:tool_call` hook family.
|
|
37
|
+
|
|
38
|
+
There is **no** `text?` predicate and **no** `content` accessor on `RobotResult`.
|
|
17
39
|
|
|
18
40
|
## Token & Cost Tracking
|
|
19
41
|
|
|
@@ -47,77 +69,143 @@ result.duration # => 2.34 (always set by delegate)
|
|
|
47
69
|
result.robot_name # => "analyst"
|
|
48
70
|
result.id # => "550e8400-e29b-..." (UUID, unique per run)
|
|
49
71
|
result.created_at # => Time instance
|
|
50
|
-
result.stop_reason # =>
|
|
72
|
+
result.stop_reason # => nil — always, for results built by Robot#run
|
|
51
73
|
```
|
|
52
74
|
|
|
53
|
-
|
|
75
|
+
!!! warning "`stop_reason` is always `nil` here"
|
|
76
|
+
`build_result` sets it with
|
|
77
|
+
`response.respond_to?(:stop_reason) ? response.stop_reason : nil`, and
|
|
78
|
+
`RubyLLM::Message` does **not** define `stop_reason`. So every
|
|
79
|
+
`robot.run` result carries `nil`, and `export` drops the key entirely.
|
|
80
|
+
(`RobotLab::Message::VALID_STOP_REASONS` is `["tool", "stop"]`, but that
|
|
81
|
+
constant governs the message classes you construct yourself — it is not
|
|
82
|
+
what a `RobotResult` reports.)
|
|
83
|
+
|
|
84
|
+
## Inspecting the Output
|
|
54
85
|
|
|
55
86
|
```ruby
|
|
56
87
|
result.output.each do |message|
|
|
57
|
-
puts message.role #
|
|
58
|
-
puts message.content # String
|
|
88
|
+
puts message.role # "assistant"
|
|
89
|
+
puts message.content # String
|
|
59
90
|
end
|
|
60
91
|
|
|
61
|
-
result.has_tool_calls? # =>
|
|
92
|
+
result.has_tool_calls? # => output.any?(&:tool_call?) || tool_calls.any?
|
|
62
93
|
result.stopped? # => true if execution ended naturally (not mid-tool-call)
|
|
63
94
|
```
|
|
64
95
|
|
|
96
|
+
Because `stop_reason` is always `nil` on a `Robot#run` result, `stopped?`
|
|
97
|
+
collapses to `!has_tool_calls?` in practice — and since `tool_calls` is
|
|
98
|
+
effectively always empty, it is normally `true`.
|
|
99
|
+
|
|
65
100
|
## Persistence
|
|
66
101
|
|
|
67
|
-
Export for serialization (excludes debug fields):
|
|
102
|
+
Export for serialization (excludes the debug fields):
|
|
68
103
|
|
|
69
104
|
```ruby
|
|
70
105
|
hash = result.export
|
|
71
106
|
# {
|
|
72
107
|
# robot_name: "analyst",
|
|
108
|
+
# delegated_by: "manager",
|
|
73
109
|
# output: [...],
|
|
74
110
|
# tool_calls: [...],
|
|
75
111
|
# created_at: "2026-04-18T12:00:00Z",
|
|
76
112
|
# id: "550e8400-...",
|
|
77
113
|
# checksum: "a1b2c3...",
|
|
78
|
-
# stop_reason
|
|
114
|
+
# # stop_reason is absent — it is nil, and export is .compact-ed
|
|
79
115
|
# duration: 2.34,
|
|
80
116
|
# input_tokens: 512,
|
|
81
117
|
# output_tokens: 128
|
|
82
118
|
# }
|
|
83
119
|
|
|
84
|
-
json = result.to_json
|
|
120
|
+
json = result.to_json # uses export
|
|
121
|
+
|
|
122
|
+
hash_with_debug = result.to_h # export + prompt/history/raw, also compacted
|
|
85
123
|
|
|
86
124
|
# Reconstruct from hash
|
|
87
125
|
restored = RobotLab::RobotResult.from_hash(hash)
|
|
88
126
|
```
|
|
89
127
|
|
|
90
|
-
`
|
|
128
|
+
!!! warning "`export` is `.compact`ed"
|
|
129
|
+
Keys whose value is `nil` are dropped entirely. `delegated_by` and
|
|
130
|
+
`duration` disappear when unset, and `stop_reason` is dropped from
|
|
131
|
+
**every** `Robot#run` result because it is always `nil`. `input_tokens` and
|
|
132
|
+
`output_tokens` are additionally converted to `nil` (and therefore dropped)
|
|
133
|
+
when they are **zero**, so a provider that reports no usage yields a hash
|
|
134
|
+
with no token keys at all:
|
|
135
|
+
|
|
136
|
+
```ruby
|
|
137
|
+
RobotLab::RobotResult.new(robot_name: "a", output: []).export
|
|
138
|
+
# => { robot_name: "a", output: [], tool_calls: [],
|
|
139
|
+
# created_at: "...", id: "...", checksum: "..." }
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
Only `robot_name`, `output`, `tool_calls`, `created_at`, `id`, and `checksum`
|
|
143
|
+
are always present. Code reading an exported hash must tolerate missing keys.
|
|
144
|
+
|
|
145
|
+
`checksum` is a SHA-256 hex digest of `{ output:, tool_calls:, created_at: }`
|
|
146
|
+
(with `created_at` reduced to an integer epoch). Use it for deduplication when
|
|
147
|
+
persisting results.
|
|
91
148
|
|
|
92
149
|
## Debug Fields
|
|
93
150
|
|
|
94
|
-
|
|
151
|
+
All three are read/write and excluded from `export`/`to_json` (but included in `to_h`):
|
|
95
152
|
|
|
96
153
|
```ruby
|
|
97
|
-
result.prompt # Array<Message
|
|
98
|
-
result.history # Array<Message
|
|
99
|
-
result.raw # raw
|
|
154
|
+
result.prompt # Array<Message>, nil — nil unless you assign it
|
|
155
|
+
result.history # Array<Message>, nil — nil unless you assign it
|
|
156
|
+
result.raw # the raw ruby_llm response; Robot#run always sets this
|
|
100
157
|
```
|
|
101
158
|
|
|
159
|
+
`raw` is populated by `Robot#build_result` on every run, so it is the escape
|
|
160
|
+
hatch for anything RobotLab does not surface (cost, provider metadata, the
|
|
161
|
+
untouched message object). `prompt` and `history` are never populated by the
|
|
162
|
+
framework.
|
|
163
|
+
|
|
102
164
|
## Attribute Reference
|
|
103
165
|
|
|
104
|
-
| Attribute | Type | Description |
|
|
105
|
-
|
|
106
|
-
| `robot_name` | String | Name of the robot that produced this result |
|
|
107
|
-
| `
|
|
108
|
-
| `output` | Array\<
|
|
109
|
-
| `tool_calls` | Array\<ToolResultMessage\> | Tool call results |
|
|
110
|
-
| `input_tokens` | Integer | Tokens sent to LLM |
|
|
111
|
-
| `output_tokens` | Integer | Tokens generated |
|
|
112
|
-
| `
|
|
113
|
-
| `
|
|
114
|
-
| `
|
|
115
|
-
| `
|
|
116
|
-
| `
|
|
117
|
-
| `
|
|
166
|
+
| Attribute | Access | Type | Description |
|
|
167
|
+
|-----------|--------|------|-------------|
|
|
168
|
+
| `robot_name` | r | String | Name of the robot that produced this result |
|
|
169
|
+
| `last_text_content` | r | String, nil | Content of the last text message in `output` (alias: `reply`) |
|
|
170
|
+
| `output` | r | Array\<TextMessage\> | The final response text, as a one-element array (or empty) |
|
|
171
|
+
| `tool_calls` | r | Array\<ToolResultMessage\> | Tool call results — effectively always empty |
|
|
172
|
+
| `input_tokens` | r | Integer | Tokens sent to LLM (0 when the provider reports no usage) |
|
|
173
|
+
| `output_tokens` | r | Integer | Tokens generated (0 when not reported) |
|
|
174
|
+
| `id` | r | String | UUID, generated per result |
|
|
175
|
+
| `created_at` | r | Time | Creation timestamp |
|
|
176
|
+
| `stop_reason` | r | String, nil | Always `nil` for `Robot#run` results (the ruby_llm response has no `stop_reason`); settable only when you build a `RobotResult` yourself |
|
|
177
|
+
| `duration` | rw | Float, nil | Elapsed seconds — set by `delegate` and by `Robot#call` in a pipeline; `nil` for a direct `robot.run` |
|
|
178
|
+
| `delegated_by` | rw | String, nil | Delegating robot's name — set by `delegate` |
|
|
179
|
+
| `prompt` | rw | Array\<Message\>, nil | Debug field, `nil` unless assigned |
|
|
180
|
+
| `history` | rw | Array\<Message\>, nil | Debug field, `nil` unless assigned |
|
|
181
|
+
| `raw` | rw | Object, nil | The raw ruby_llm response; set by `Robot#build_result` |
|
|
182
|
+
|
|
183
|
+
Methods: `checksum`, `export`, `to_h`, `to_json`, `has_tool_calls?`, `stopped?`,
|
|
184
|
+
and the class method `RobotResult.from_hash`.
|
|
185
|
+
|
|
186
|
+
## Constructor
|
|
187
|
+
|
|
188
|
+
```ruby
|
|
189
|
+
RobotLab::RobotResult.new(
|
|
190
|
+
robot_name:, # required
|
|
191
|
+
output:, # required — Array<Message, Hash>
|
|
192
|
+
tool_calls: [],
|
|
193
|
+
created_at: nil, # defaults to Time.now
|
|
194
|
+
id: nil, # defaults to SecureRandom.uuid
|
|
195
|
+
prompt: nil,
|
|
196
|
+
history: nil,
|
|
197
|
+
raw: nil,
|
|
198
|
+
stop_reason: nil,
|
|
199
|
+
input_tokens: 0,
|
|
200
|
+
output_tokens: 0
|
|
201
|
+
)
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
`output` entries must be `Message` instances or Hashes (`Message.from_hash`);
|
|
205
|
+
anything else raises `ArgumentError`.
|
|
118
206
|
|
|
119
207
|
## Related
|
|
120
208
|
|
|
121
|
-
- [Robot API](robot.md) — `run
|
|
209
|
+
- [Robot API](robot.md) — [`run`](robot.md#run), [`delegate`](robot.md#delegate), [`compress_history`](robot.md#compress_history)
|
|
122
210
|
- [Building Robots](../../guides/building-robots.md) — Robot construction patterns
|
|
123
211
|
- [Structured Delegation](../../guides/building-robots.md#structured-delegation) — `DelegationFuture` and async fan-out
|