Example 11: Collaborative Robots¶
Three robots collaborate through a shared message bus to build a weather data pipeline. Each robot's methods are generated by the LLM based on detailed task descriptions.
Source: examples/11_collaborative_robots/
Architecture¶
flowchart LR
A[Atlas] -->|weather data| B[Nova]
B -->|analyzed data| C[Echo]
C -->|formatted report| D[Output]
style A fill:#6366f1,color:#fff
style B fill:#8b5cf6,color:#fff
style C fill:#a855f7,color:#fff
Three robots connected by a MessageBus:
| Robot | Role | Methods Generated |
|---|---|---|
| Atlas | Data generator | generate_weather_data, summarize_raw_data |
| Nova | Analyzer | compute_basic_statistics, classify_conditions |
| Echo | Reporter | format_weather_report |
Two-Layer LLM Approach¶
This example uses a two-layer approach:
- Layer 1 (Analyze): A direct
RubyLLM.chat().ask()call decomposes the task description into a JSON array of method specifications - Layer 2 (Generate): Loops through the specs and calls
_()for each one. SelfAgency handles shape, generate, validate, and sandbox eval
The task descriptions are prescriptive -- they dictate exact method names, parameters, algorithms, and data structures.
Robot Class¶
The Robot class includes SelfAgency and adds:
- Task decomposition via
analyze_task-- asks the LLM to parse a task into method specs - Pipeline execution via
execute-- chains generated methods by arity - Message bus --
send_message,receive_message,broadcast - Generation logging via
on_method_generated - Persistence via
_save!-- saves each robot as a subclass
class Robot
include SelfAgency
attr_reader :name, :task, :bus, :inbox, :capabilities, :generation_log
def initialize(name:, task:, bus:)
@name = name
@task = task
@bus = bus
# ... decompose task, generate methods
end
def execute(input = nil)
# Chain capabilities by arity
end
end
Pipeline Execution¶
# Step 1: Atlas generates weather data
atlas_result = atlas.execute
atlas.send_message(to: "Nova", content: atlas_result)
# Step 2: Nova analyzes the data
nova_input = nova.inbox.last&.dig(:content)
nova_result = nova.execute(nova_input)
nova.send_message(to: "Echo", content: nova_result)
# Step 3: Echo formats the report
echo_input = echo.inbox.last&.dig(:content)
final_report = echo.execute(echo_input)
Saving Robots¶
After the pipeline runs, each robot's generated methods are saved as a subclass file:
[atlas, nova, echo].each do |robot|
path = robot._save!(as: robot.name)
puts "#{robot.name}: saved to #{path}"
end
This produces files like atlas.rb, nova.rb, echo.rb, each containing a Robot subclass with the generated methods baked in.
Key Difference from Example 12¶
In this example, the task descriptions dictate method names and algorithms. The LLM is told exactly what to implement. Compare with Example 12: Autonomous Robots, where robots receive only a high-level goal and the LLM decides everything.