Module: RobotLab::Robot::BusMessaging
- Defined in:
- lib/robot_lab/robot/bus_messaging.rb
Overview
Inter-robot communication via TypedBus.
Expects the including class to provide:
@bus, @bus_poller, @bus_poller_group, @message_counter,
@outbox, @message_handler, @bus_subscriber_id, @name
and the `run` instance method.
Delivery Serialization
TypedBus delivers messages in concurrent Async fibers. Robots enqueue deliveries into a BusPoller rather than handling them inline. The BusPoller drains each group's queue sequentially on a dedicated OS thread, so robot.run() calls never interleave.
Owns: @bus, @bus_poller, @private_bus_poller, @bus_poller_group, @bus_subscriber_id, @message_counter, @outbox, @message_handler Reads: @name Contract: ivars initialized by initialize_runtime_state before first bus operation
Instance Method Summary collapse
-
#assign_bus_poller(poller, group: :default) ⇒ void
Assign a shared BusPoller from a Network.
-
#inherited_llm_settings ⇒ Hash
Model/provider a spawned robot inherits from its parent so a specialist runs on the SAME LLM as the robot that spawned it (e.g. a local Ollama model) instead of falling back to the global default.
-
#on_message {|message| ... } ⇒ self
Register a custom handler for incoming bus messages.
-
#respond_to_tasks(auto_reply: true) {|message| ... } ⇒ self
Automatically respond to inbound (non-reply) bus tasks: run
responderto produce a reply, and send it back to the sender. -
#send_message(to:, content:) ⇒ RobotMessage
Send a message to another robot via the bus.
-
#send_reply(to:, content:, in_reply_to:) ⇒ RobotMessage
Send a reply to a specific message via the bus.
-
#serve(auto_reply: true) ⇒ self
Serve inbound bus tasks by running each through this member's #run and replying with the result — the one-call way to make a Robot cooperate on the bus the way a Cyborg already does out of the box.
-
#spawn(name: "robot", system_prompt: nil, template: nil, local_tools: []) ⇒ Robot
Spawn a new robot on a shared bus.
-
#with_bus(bus = nil) ⇒ self
Connect this robot to a message bus.
Instance Method Details
#assign_bus_poller(poller, group: :default) ⇒ void
This method returns an undefined value.
Assign a shared BusPoller from a Network.
Stops any private poller this robot auto-created, then adopts the network's shared poller for the given group.
175 176 177 178 179 180 |
# File 'lib/robot_lab/robot/bus_messaging.rb', line 175 def assign_bus_poller(poller, group: :default) @private_bus_poller&.stop @private_bus_poller = nil @bus_poller = poller @bus_poller_group = group end |
#inherited_llm_settings ⇒ Hash
Model/provider a spawned robot inherits from its parent so a specialist runs on the SAME LLM as the robot that spawned it (e.g. a local Ollama model) instead of falling back to the global default. Caller-supplied opts override these.
141 142 143 144 145 146 |
# File 'lib/robot_lab/robot/bus_messaging.rb', line 141 def inherited_llm_settings settings = {} settings[:model] = model if model settings[:provider] = provider if provider settings end |
#on_message {|message| ... } ⇒ self
Register a custom handler for incoming bus messages.
Block arity controls delivery handling:
- 1 argument
|message|: auto-acks before calling, auto-nacks on exception - 2 arguments
|delivery, message|: manual mode, you call ack!/nack!
71 72 73 74 |
# File 'lib/robot_lab/robot/bus_messaging.rb', line 71 def (&block) @message_handler = block self end |
#respond_to_tasks(auto_reply: true) {|message| ... } ⇒ self
Automatically respond to inbound (non-reply) bus tasks: run responder to
produce a reply, and send it back to the sender. This is the symmetric
counterpart to how a Cyborg answers its human — one call makes any bus
member a first-class responder instead of hand-wiring #on_message.
The responder runs on the poller drain thread, so deliveries to this member are handled one at a time (a long turn delays the next inbound message).
87 88 89 90 91 92 93 94 95 |
# File 'lib/robot_lab/robot/bus_messaging.rb', line 87 def respond_to_tasks(auto_reply: true, &responder) do || next if .reply? reply = responder.call() send_reply(to: .from, content: reply, in_reply_to: .key) if auto_reply && reply end self end |
#send_message(to:, content:) ⇒ RobotMessage
Send a message to another robot via the bus.
29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
# File 'lib/robot_lab/robot/bus_messaging.rb', line 29 def (to:, content:) raise BusError, "No bus configured on robot '#{@name}'" unless @bus # Counter + outbox are shared with the poller thread (reply correlation) # and with other senders; mutate them under the bus mutex. Publish (which # does I/O) stays outside the lock. = @bus_mutex.synchronize do @message_counter += 1 msg = RobotMessage.build(id: @message_counter, from: @name, content: content) @outbox[msg.key] = { message: msg, status: :sent, replies: [] } msg end publish_to_bus(to.to_sym, ) end |
#send_reply(to:, content:, in_reply_to:) ⇒ RobotMessage
Send a reply to a specific message via the bus.
52 53 54 55 56 57 58 59 60 61 |
# File 'lib/robot_lab/robot/bus_messaging.rb', line 52 def send_reply(to:, content:, in_reply_to:) raise BusError, "No bus configured on robot '#{@name}'" unless @bus reply = @bus_mutex.synchronize do @message_counter += 1 RobotMessage.build(id: @message_counter, from: @name, content: content, in_reply_to: in_reply_to) end publish_to_bus(to.to_sym, reply) reply end |
#serve(auto_reply: true) ⇒ self
Serve inbound bus tasks by running each through this member's #run and replying with the result — the one-call way to make a Robot cooperate on the bus the way a Cyborg already does out of the box.
103 104 105 |
# File 'lib/robot_lab/robot/bus_messaging.rb', line 103 def serve(auto_reply: true) respond_to_tasks(auto_reply: auto_reply) { || run(bus_task_content()).reply } end |
#spawn(name: "robot", system_prompt: nil, template: nil, local_tools: []) ⇒ Robot
Spawn a new robot on a shared bus.
Creates a new Robot instance that shares this robot's bus, allowing it to immediately send and receive messages with all other robots on the bus. If no bus exists yet, one is created automatically and the parent robot is connected to it.
121 122 123 124 125 126 127 128 129 130 131 132 133 |
# File 'lib/robot_lab/robot/bus_messaging.rb', line 121 def spawn(name: "robot", system_prompt: nil, template: nil, local_tools: [], **) ensure_bus RobotLab.build( name: name, system_prompt: system_prompt, template: template, local_tools: local_tools, bus: @bus, **inherited_llm_settings, ** ) end |
#with_bus(bus = nil) ⇒ self
Connect this robot to a message bus.
If a bus is provided, the robot joins it. If no bus is provided and the robot doesn't already have one, a new bus is created. No-op if the robot is already on the given bus.
157 158 159 160 161 162 163 164 |
# File 'lib/robot_lab/robot/bus_messaging.rb', line 157 def with_bus(bus = nil) return self if bus && @bus == bus teardown_bus_channel if @bus @bus = bus || @bus || TypedBus::MessageBus.new setup_bus_channel self end |