Class: HTM::RobotGroup
- Inherits:
-
Object
- Object
- HTM::RobotGroup
- Defined in:
- lib/htm/robot_group.rb
Overview
Coordinates multiple robots with shared working memory and automatic failover.
RobotGroup provides application-level coordination for multiple HTM robots, enabling them to share a common working memory context. Key capabilities include:
-
**Shared Working Memory**: All group members have access to the same context
-
**Active/Passive Roles**: Active robots participate in conversations; passive robots maintain synchronized context for instant failover
-
**Real-time Sync**: PostgreSQL LISTEN/NOTIFY enables immediate synchronization
-
Failover: When an active robot fails, a passive robot takes over instantly
-
**Dynamic Scaling**: Add or remove robots at runtime
Instance Attribute Summary collapse
-
#channel ⇒ HTM::WorkingMemoryChannel
readonly
The pub/sub channel used for real-time synchronization.
-
#max_tokens ⇒ Integer
readonly
Maximum token budget for working memory.
-
#name ⇒ String
readonly
Name of the robot group.
Membership Management collapse
-
#active?(robot_name) ⇒ Boolean
Checks if a robot is an active member of this group.
-
#active_robot_names ⇒ Array<String>
Returns names of all active robots.
-
#add_active(robot_name) ⇒ Integer
Adds a robot as an active member of the group.
-
#add_passive(robot_name) ⇒ Integer
Adds a robot as a passive (standby) member of the group.
-
#demote(robot_name) ⇒ void
Demotes an active robot to passive status.
-
#member?(robot_name) ⇒ Boolean
Checks if a robot is a member of this group.
-
#member_ids ⇒ Array<Integer>
Returns database IDs of all group members.
-
#passive?(robot_name) ⇒ Boolean
Checks if a robot is a passive member of this group.
-
#passive_robot_names ⇒ Array<String>
Returns names of all passive robots.
-
#promote(robot_name) ⇒ void
Promotes a passive robot to active status.
-
#remove(robot_name) ⇒ void
Removes a robot from the group.
Shared Working Memory Operations collapse
-
#clear_working_memory ⇒ Integer
Clears shared working memory for all group members.
-
#recall(query) ⇒ Array
Recalls memories from shared working memory.
-
#remember(content, originator: nil) ⇒ Integer
Adds content to shared working memory for all group members.
-
#working_memory_contents ⇒ Sequel::Dataset
Returns all nodes currently in shared working memory.
Synchronization collapse
-
#in_sync? ⇒ Boolean
Checks if all members have identical working memory.
-
#sync_all ⇒ Hash
Synchronizes all members to a consistent state.
-
#sync_robot(robot_name) ⇒ Integer
Synchronizes a specific robot to match the group’s shared working memory.
Failover collapse
-
#failover! ⇒ String
Performs automatic failover to the first passive robot.
-
#transfer_working_memory(from_robot, to_robot, clear_source: true) ⇒ Integer
Transfers working memory from one robot to another.
Status & Health collapse
-
#status ⇒ Hash
Returns comprehensive status information about the group.
-
#sync_stats ⇒ Hash
Returns statistics about real-time synchronization.
Instance Method Summary collapse
-
#initialize(name:, active: [], passive: [], max_tokens: 4000, db_config: nil) ⇒ RobotGroup
constructor
Creates a new robot group with optional initial members.
-
#shutdown ⇒ void
Shuts down the group by stopping the listener thread.
Constructor Details
#initialize(name:, active: [], passive: [], max_tokens: 4000, db_config: nil) ⇒ RobotGroup
Creates a new robot group with optional initial members.
Initializes the group, sets up the PostgreSQL pub/sub channel for real-time synchronization, and registers initial active and passive robots.
77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 |
# File 'lib/htm/robot_group.rb', line 77 def initialize(name:, active: [], passive: [], max_tokens: 4000, db_config: nil) @name = name @max_tokens = max_tokens @active_robots = {} # name => HTM instance @passive_robots = {} # name => HTM instance @sync_stats = { nodes_synced: 0, evictions_synced: 0 } @mutex = Mutex.new # Setup pub/sub channel for real-time sync @db_config = db_config || HTM::Database.default_config @channel = HTM::WorkingMemoryChannel.new(name, @db_config) # Subscribe to working memory changes setup_sync_listener # Start listening for notifications @channel.start_listening # Initialize robots active.each { |robot_name| add_active(robot_name) } passive.each { |robot_name| add_passive(robot_name) } end |
Instance Attribute Details
#channel ⇒ HTM::WorkingMemoryChannel (readonly)
The pub/sub channel used for real-time synchronization
51 52 53 |
# File 'lib/htm/robot_group.rb', line 51 def channel @channel end |
#max_tokens ⇒ Integer (readonly)
Maximum token budget for working memory
47 48 49 |
# File 'lib/htm/robot_group.rb', line 47 def max_tokens @max_tokens end |
#name ⇒ String (readonly)
Name of the robot group
43 44 45 |
# File 'lib/htm/robot_group.rb', line 43 def name @name end |
Instance Method Details
#active?(robot_name) ⇒ Boolean
Checks if a robot is an active member of this group.
249 250 251 |
# File 'lib/htm/robot_group.rb', line 249 def active?(robot_name) @active_robots.key?(robot_name) end |
#active_robot_names ⇒ Array<String>
Returns names of all active robots.
283 284 285 |
# File 'lib/htm/robot_group.rb', line 283 def active_robot_names @active_robots.keys end |
#add_active(robot_name) ⇒ Integer
Adds a robot as an active member of the group.
Active robots can add memories and respond to queries. The new robot is automatically synchronized with existing shared working memory.
129 130 131 132 133 134 135 136 137 138 139 |
# File 'lib/htm/robot_group.rb', line 129 def add_active(robot_name) raise ArgumentError, "#{robot_name} is already a member" if member?(robot_name) htm = HTM.new(robot_name: robot_name, working_memory_size: @max_tokens) @active_robots[robot_name] = htm # Sync existing shared working memory to new member sync_robot(robot_name) if member_ids.length > 1 htm.robot_id end |
#add_passive(robot_name) ⇒ Integer
Adds a robot as a passive (standby) member of the group.
Passive robots maintain synchronized working memory but don’t actively participate in conversations. They serve as warm standbys for failover.
153 154 155 156 157 158 159 160 161 162 163 |
# File 'lib/htm/robot_group.rb', line 153 def add_passive(robot_name) raise ArgumentError, "#{robot_name} is already a member" if member?(robot_name) htm = HTM.new(robot_name: robot_name, working_memory_size: @max_tokens) @passive_robots[robot_name] = htm # Sync existing shared working memory to new member sync_robot(robot_name) if member_ids.length > 1 htm.robot_id end |
#clear_working_memory ⇒ Integer
Clears shared working memory for all group members.
Updates database flags and notifies all members to clear their in-memory caches.
397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 |
# File 'lib/htm/robot_group.rb', line 397 def clear_working_memory count = HTM::Models::RobotNode .where(robot_id: member_ids, working_memory: true) .update(working_memory: false) # Clear in-memory working memory for primary robot primary = @active_robots.values.first || @passive_robots.values.first return 0 unless primary primary.clear_working_memory # Notify all listeners (will clear other in-memory caches via callback) @channel.notify(:cleared, node_id: nil, robot_id: primary.robot_id) count end |
#demote(robot_name) ⇒ void
This method returns an undefined value.
Demotes an active robot to passive status.
The robot retains its working memory but stops handling queries. Cannot demote the last active robot.
220 221 222 223 224 225 226 |
# File 'lib/htm/robot_group.rb', line 220 def demote(robot_name) raise ArgumentError, "#{robot_name} is not an active member" unless active?(robot_name) raise ArgumentError, 'Cannot demote last active robot' if @active_robots.length == 1 htm = @active_robots.delete(robot_name) @passive_robots[robot_name] = htm end |
#failover! ⇒ String
Performs automatic failover to the first passive robot.
Promotes the first passive robot to active status. The promoted robot already has synchronized working memory and can immediately handle requests.
589 590 591 592 593 594 595 596 597 598 599 600 |
# File 'lib/htm/robot_group.rb', line 589 def failover! raise 'No passive robots available for failover' if @passive_robots.empty? # Get first passive robot standby_name = @passive_robots.keys.first # Promote it promote(standby_name) puts " ⚡ Failover: #{standby_name} promoted to active" standby_name end |
#in_sync? ⇒ Boolean
Checks if all members have identical working memory.
Compares the set of working memory node IDs across all members.
501 502 503 504 505 506 507 508 509 510 511 512 513 514 |
# File 'lib/htm/robot_group.rb', line 501 def in_sync? return true if member_ids.length <= 1 # Get working memory node_ids for each robot working_memories = member_ids.map do |robot_id| HTM::Models::RobotNode .where(robot_id: robot_id, working_memory: true) .select_map(:node_id) .sort end # All should be identical working_memories.uniq.length == 1 end |
#member?(robot_name) ⇒ Boolean
Checks if a robot is a member of this group.
237 238 239 |
# File 'lib/htm/robot_group.rb', line 237 def member?(robot_name) @active_robots.key?(robot_name) || @passive_robots.key?(robot_name) end |
#member_ids ⇒ Array<Integer>
Returns database IDs of all group members.
272 273 274 |
# File 'lib/htm/robot_group.rb', line 272 def member_ids all_robots.values.map(&:robot_id) end |
#passive?(robot_name) ⇒ Boolean
Checks if a robot is a passive member of this group.
261 262 263 |
# File 'lib/htm/robot_group.rb', line 261 def passive?(robot_name) @passive_robots.key?(robot_name) end |
#passive_robot_names ⇒ Array<String>
Returns names of all passive robots.
294 295 296 |
# File 'lib/htm/robot_group.rb', line 294 def passive_robot_names @passive_robots.keys end |
#promote(robot_name) ⇒ void
This method returns an undefined value.
Promotes a passive robot to active status.
The robot retains its synchronized working memory and becomes eligible to handle queries and add memories.
199 200 201 202 203 204 |
# File 'lib/htm/robot_group.rb', line 199 def promote(robot_name) raise ArgumentError, "#{robot_name} is not a passive member" unless passive?(robot_name) htm = @passive_robots.delete(robot_name) @active_robots[robot_name] = htm end |
#recall(query) ⇒ Array
Recalls memories from shared working memory.
Uses the first active robot to perform the query against the shared working memory context.
359 360 361 362 363 364 |
# File 'lib/htm/robot_group.rb', line 359 def recall(query, **) raise 'No active robots in group' if @active_robots.empty? primary = @active_robots.values.first primary.recall(query, **) end |
#remember(content, originator: nil) ⇒ Integer
Adds content to shared working memory for all group members.
The memory is created by the specified originator (or first active robot) and automatically synchronized to all other members via database and real-time notifications.
323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 |
# File 'lib/htm/robot_group.rb', line 323 def remember(content, originator: nil, **) raise 'No active robots in group' if @active_robots.empty? # Use first active robot (or specified originator) to create the memory primary = if originator && all_robots[originator] all_robots[originator] else @active_robots.values.first end node_id = primary.remember(content, **) # Sync to database (robot_nodes table) for all other members sync_node_to_members(node_id, exclude: primary.robot_id) # Notify all listeners via PostgreSQL NOTIFY (triggers in-memory sync) @channel.notify(:added, node_id: node_id, robot_id: primary.robot_id) node_id end |
#remove(robot_name) ⇒ void
This method returns an undefined value.
Removes a robot from the group.
Clears the robot’s working memory flags in the database. The robot can be either active or passive.
176 177 178 179 180 181 182 183 184 |
# File 'lib/htm/robot_group.rb', line 176 def remove(robot_name) htm = @active_robots.delete(robot_name) || @passive_robots.delete(robot_name) return unless htm # Clear working memory flags for this robot HTM::Models::RobotNode .where(robot_id: htm.robot_id, working_memory: true) .update(working_memory: false) end |
#shutdown ⇒ void
This method returns an undefined value.
Shuts down the group by stopping the listener thread.
Should be called when the group is no longer needed to release resources and close the PostgreSQL listener connection.
110 111 112 |
# File 'lib/htm/robot_group.rb', line 110 def shutdown @channel.stop_listening end |
#status ⇒ Hash
Returns comprehensive status information about the group.
625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 |
# File 'lib/htm/robot_group.rb', line 625 def status wm_contents = working_memory_contents token_count = wm_contents.sum { |n| n.token_count || 0 } { name: @name, active: active_robot_names, passive: passive_robot_names, total_members: member_ids.length, working_memory_nodes: wm_contents.count, working_memory_tokens: token_count, max_tokens: @max_tokens, token_utilization: @max_tokens.positive? ? (token_count.to_f / @max_tokens).round(2) : 0, in_sync: in_sync? } end |
#sync_all ⇒ Hash
Synchronizes all members to a consistent state.
Ensures every member has access to all shared working memory nodes.
473 474 475 476 477 478 479 480 481 482 483 484 485 486 |
# File 'lib/htm/robot_group.rb', line 473 def sync_all members_updated = 0 total_synced = 0 all_robots.each_key do |robot_name| synced = sync_robot(robot_name) if synced.positive? members_updated += 1 total_synced += synced end end { synced_nodes: total_synced, members_updated: members_updated } end |
#sync_robot(robot_name) ⇒ Integer
Synchronizes a specific robot to match the group’s shared working memory.
Copies working memory flags from other members to the specified robot, ensuring it has access to all shared context.
431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 |
# File 'lib/htm/robot_group.rb', line 431 def sync_robot(robot_name) htm = all_robots[robot_name] raise ArgumentError, "#{robot_name} is not a member" unless htm # Get all node_ids currently in any member's working memory shared_node_ids = HTM::Models::RobotNode .where(robot_id: member_ids, working_memory: true) .exclude(robot_id: htm.robot_id) .distinct .select_map(:node_id) synced = 0 shared_node_ids.each do |node_id| # Create or update robot_node with working_memory=true robot_node = HTM::Models::RobotNode.first( robot_id: htm.robot_id, node_id: node_id ) robot_node ||= HTM::Models::RobotNode.new( robot_id: htm.robot_id, node_id: node_id ) next if robot_node.working_memory robot_node.working_memory = true robot_node.save synced += 1 end synced end |
#sync_stats ⇒ Hash
Returns statistics about real-time synchronization.
651 652 653 |
# File 'lib/htm/robot_group.rb', line 651 def sync_stats @mutex.synchronize { @sync_stats.dup } end |
#transfer_working_memory(from_robot, to_robot, clear_source: true) ⇒ Integer
Transfers working memory from one robot to another.
Copies all working memory node references from the source robot to the target robot, optionally clearing the source.
540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 |
# File 'lib/htm/robot_group.rb', line 540 def transfer_working_memory(from_robot, to_robot, clear_source: true) from_htm = all_robots[from_robot] to_htm = all_robots[to_robot] raise ArgumentError, "#{from_robot} is not a member" unless from_htm raise ArgumentError, "#{to_robot} is not a member" unless to_htm # Get source's working memory nodes source_node_ids = HTM::Models::RobotNode .where(robot_id: from_htm.robot_id, working_memory: true) .select_map(:node_id) transferred = 0 source_node_ids.each do |node_id| robot_node = HTM::Models::RobotNode.first( robot_id: to_htm.robot_id, node_id: node_id ) robot_node ||= HTM::Models::RobotNode.new( robot_id: to_htm.robot_id, node_id: node_id ) robot_node.working_memory = true robot_node.save transferred += 1 end # Clear source's working memory if requested if clear_source HTM::Models::RobotNode .where(robot_id: from_htm.robot_id, working_memory: true) .update(working_memory: false) end transferred end |
#working_memory_contents ⇒ Sequel::Dataset
Returns all nodes currently in shared working memory.
Queries the database for the union of all members’ working memory, returning nodes sorted by creation date (newest first).
377 378 379 380 381 382 383 384 |
# File 'lib/htm/robot_group.rb', line 377 def working_memory_contents node_ids = HTM::Models::RobotNode .where(robot_id: member_ids, working_memory: true) .distinct .select_map(:node_id) HTM::Models::Node.where(id: node_ids).order(Sequel.desc(:created_at)) end |