Skip to content

Hive Mind Architecture: Multi-Robot Shared Memory

HTM implements a "hive mind" architecture where multiple robots (AI agents) share a global memory database. This enables cross-robot learning, context continuity across sessions, and collaborative knowledge building without requiring users to repeat information.

Overview

In the hive mind model, all robots access a single shared long-term memory database while maintaining independent working memory for process isolation. This design provides the best of both worlds: global knowledge sharing with local performance optimization.

Hive Mind: Shared Long-Term Memory

Long-Term Memory PostgreSQL Shared Global Database All Robots Access Here

Robot 1: Code Helper ID: robot-abc123 Own Working Memory

Robot 2: Research Bot ID: robot-xyz789 Own Working Memory

Robot 3: Chat Bot ID: robot-def456 Own Working Memory

Robot 4: Designer ID: robot-ghi012 Own Working Memory

read/write read/write read/write read/write

Knowledge Sharing: All robots see all memories

Related ADR

See ADR-004: Multi-Robot Shared Memory (Hive Mind) for the complete architectural decision record.

Why Hive Mind?

Problems with Isolated Memory

When each robot has independent memory:

  • Users repeat information across robots
  • Context lost when switching robots
  • No cross-robot learning
  • Fragmented conversation history
  • Architectural decisions made by one robot unknown to others

Benefits of Shared Memory

With the hive mind architecture:

  • Context continuity: User never repeats themselves
  • Cross-robot learning: Knowledge compounds across agents
  • Seamless switching: Switch robots without losing context
  • Unified knowledge base: Single source of truth
  • Collaborative development: Robots build on each other's work

User Experience

With shared memory, users can switch from a code helper to a research assistant without explaining the project context again. The research assistant already knows what the code helper learned.

Architecture Design

Memory Topology

HTM uses a hybrid memory topology:

  • Long-Term Memory: Shared globally across all robots
  • Working Memory: Per-robot, process-local

Memory Topology: Shared LTM + Local WM

Shared (Global) Per-Robot (Local)

Robot 1 (Process 1) Working Memory In-memory, token-limited Independent

Robot 2 (Process 2) Working Memory In-memory, token-limited Independent

Long-Term Memory (Shared) PostgreSQL All robots read/write here Memories attributed with robot_id Single Source of Truth

read/write read/write

Each robot has fast local cache (WM) + access to global knowledge (LTM)

Why This Design?

Shared Long-Term Memory:

  • Global knowledge base accessible to all robots
  • Cross-robot context continuity
  • Simplified architecture (single database)
  • Unified search across all conversations

Per-Robot Working Memory:

  • Fast O(1) local access without network overhead
  • Process isolation (no distributed state synchronization)
  • Independent token budgets per robot
  • Simple implementation (Ruby Hash, no Redis needed)

Design Trade-off

This architecture optimizes for single-user, multi-robot scenarios. For multi-tenant deployments, add row-level security or database sharding by tenant_id.

Robot Identification System

Every robot in the hive mind has a unique identity for attribution tracking and activity monitoring.

Dual Identifier System

HTM uses two identifiers for each robot:

1. Robot ID (robot_id)

  • Type: UUID v4 (RFC 4122)
  • Format: "f47ac10b-58cc-4372-a567-0e02b2c3d479"
  • Generation: SecureRandom.uuid if not provided
  • Purpose: Primary key, foreign key references, attribution
  • Uniqueness: Guaranteed (collision probability: ~10^-36)

2. Robot Name (robot_name)

  • Type: String (human-readable)
  • Format: Any descriptive string (e.g., "Code Helper", "Research Assistant")
  • Generation: "robot_#{robot_id[0..7]}" if not provided
  • Purpose: Display, debugging, logging
  • Uniqueness: Not enforced (names can collide)

Robot Initialization

# Option 1: Auto-generated identity (ephemeral robot)
htm = HTM.new(robot_name: "Code Helper")
# robot_id: auto-generated UUID (new each session)
# robot_name: "Code Helper"

# Option 2: Persistent identity (stable robot)
ROBOT_ID = ENV['ROBOT_ID'] || "f47ac10b-58cc-4372-a567-0e02b2c3d479"
htm = HTM.new(
  robot_id: ROBOT_ID,
  robot_name: "Code Helper"
)
# robot_id: same across sessions
# robot_name: "Code Helper"

# Option 3: Minimal (auto-generate everything)
htm = HTM.new
# robot_id: auto-generated UUID
# robot_name: "robot_f47ac10b" (derived from UUID)

Persistent vs Ephemeral Robots

  • Ephemeral: New UUID every session. Useful for testing or one-off tasks.
  • Persistent: Store robot_id in config/environment. Recommended for production robots with stable identities.

Robot Registry

All robots are registered in the robots table on first initialization:

CREATE TABLE robots (
  id TEXT PRIMARY KEY,              -- robot_id (UUID)
  name TEXT,                         -- robot_name (human-readable)
  created_at TIMESTAMP DEFAULT NOW(),
  last_active TIMESTAMP DEFAULT NOW(),
  metadata JSONB                     -- future extensibility
);

Registration flow:

def register_robot
  @long_term_memory.register_robot(@robot_id, @robot_name)
end

# SQL: UPSERT semantics
# INSERT INTO robots (id, name) VALUES ($1, $2)
# ON CONFLICT (id) DO UPDATE
# SET name = $2, last_active = CURRENT_TIMESTAMP

Related ADR

See ADR-008: Robot Identification System for detailed design decisions.

Memory Attribution and Deduplication

HTM uses a many-to-many relationship between robots and nodes, enabling both content deduplication and attribution tracking.

Attribution Schema

-- Nodes are content-deduplicated via SHA-256 hash
CREATE TABLE nodes (
  id BIGSERIAL PRIMARY KEY,
  content TEXT NOT NULL,
  content_hash VARCHAR(64) UNIQUE,  -- SHA-256 for deduplication
  ...
);

-- Robot-node relationships tracked in join table
CREATE TABLE robot_nodes (
  id BIGSERIAL PRIMARY KEY,
  robot_id BIGINT NOT NULL REFERENCES robots(id),
  node_id BIGINT NOT NULL REFERENCES nodes(id),
  first_remembered_at TIMESTAMPTZ,  -- When robot first saw this content
  last_remembered_at TIMESTAMPTZ,   -- When robot last tried to remember
  remember_count INTEGER DEFAULT 1  -- How many times robot remembered this
);

-- Indexes for efficient queries
CREATE UNIQUE INDEX idx_robot_nodes_unique ON robot_nodes(robot_id, node_id);
CREATE INDEX idx_robot_nodes_robot_id ON robot_nodes(robot_id);
CREATE INDEX idx_robot_nodes_node_id ON robot_nodes(node_id);

Content Deduplication

When a robot remembers content:

def remember(content, tags: [])
  # 1. Compute SHA-256 hash of content
  content_hash = Digest::SHA256.hexdigest(content)

  # 2. Check if node with same hash exists
  existing_node = HTM::Models::Node.find_by(content_hash: content_hash)

  if existing_node
    # 3a. Link robot to existing node (or update remember_count)
    link_robot_to_node(robot_id: @robot_id, node: existing_node)
    return existing_node.id
  else
    # 3b. Create new node and link robot
    node = create_new_node(content, content_hash)
    link_robot_to_node(robot_id: @robot_id, node: node)
    return node.id
  end
end

Attribution Queries

Which robots remember this content?

# Find all robots that have remembered a specific node
def robots_for_node(node_id)
  HTM::Models::RobotNode
    .where(node_id: node_id)
    .includes(:robot)
    .map do |rn|
      {
        robot_name: rn.robot.name,
        first_remembered_at: rn.first_remembered_at,
        remember_count: rn.remember_count
      }
    end
end

# Example
robots_for_node(123)
# => [
#   { robot_name: "Code Helper", first_remembered_at: "2025-01-15", remember_count: 3 },
#   { robot_name: "Research Bot", first_remembered_at: "2025-01-16", remember_count: 1 }
# ]

Nodes shared by multiple robots

# Find content that multiple robots have remembered
def shared_memories(min_robots: 2, limit: 50)
  HTM::Models::Node
    .joins(:robot_nodes)
    .group('nodes.id')
    .having('COUNT(DISTINCT robot_nodes.robot_id) >= ?', min_robots)
    .order('COUNT(DISTINCT robot_nodes.robot_id) DESC')
    .limit(limit)
    .map(&:attributes)
end

# Example
shared_memories(min_robots: 2)
# => Nodes that 2+ robots have remembered

Robot activity

-- Which robots have been active?
SELECT id, name, last_active
FROM robots
ORDER BY last_active DESC;

-- Which robot has remembered the most nodes?
SELECT r.name, COUNT(rn.node_id) as memory_count
FROM robots r
LEFT JOIN robot_nodes rn ON rn.robot_id = r.id
GROUP BY r.id, r.name
ORDER BY memory_count DESC;

-- What has a specific robot remembered recently?
SELECT n.content, rn.first_remembered_at, rn.remember_count
FROM robot_nodes rn
JOIN nodes n ON n.id = rn.node_id
WHERE rn.robot_id = 1
ORDER BY rn.last_remembered_at DESC
LIMIT 50;

Cross-Robot Knowledge Sharing

The power of the hive mind lies in automatic knowledge sharing across robots.

Use Case 1: Cross-Session Context

A user works with Robot A in one session, then Robot B in another session. Robot B automatically knows what Robot A learned.

# Session 1 - Robot A (Code Helper)
htm_a = HTM.new(robot_name: "Code Helper A")
htm_a.remember("User prefers debug_me over puts for debugging")
# Stored in long-term memory, linked to robot A via robot_nodes

# === User logs out, logs in next day ===

# Session 2 - Robot B (different process, same or different machine)
htm_b = HTM.new(robot_name: "Code Helper B")

# Robot B recalls preferences
memories = htm_b.recall("debugging preference", timeframe: "last week")
# => Finds preference from Robot A!

# Robot B knows user preference without being told

Use Case 2: Collaborative Development with Deduplication

Different robots working on the same content automatically share nodes.

# Robot A (Architecture discussion)
htm_a = HTM.new(robot_name: "Architect Bot")
node_id = htm_a.remember(
  "We decided to use PostgreSQL with pgvector for HTM storage",
  tags: ["architecture", "database"]
)
# => node_id: 123 (new node created)

# Robot B learns the same fact independently
htm_b = HTM.new(robot_name: "Code Bot")
node_id = htm_b.remember(
  "We decided to use PostgreSQL with pgvector for HTM storage"
)
# => node_id: 123 (same node! Content hash matched)

# Both robots now linked to the same node
# Robot A: remember_count = 1
# Robot B: remember_count = 1

# Check shared ownership
rns = HTM::Models::RobotNode.where(node_id: 123)
rns.each { |rn| puts "Robot #{rn.robot_id}: #{rn.remember_count} times" }
# => Robot 1: 1 times
# => Robot 2: 1 times

Use Case 3: Finding Shared Knowledge

Analyze what content is shared across robots:

# Find nodes remembered by multiple robots
shared_nodes = HTM::Models::Node
  .joins(:robot_nodes)
  .group('nodes.id')
  .having('COUNT(DISTINCT robot_nodes.robot_id) >= 2')
  .select('nodes.*, COUNT(DISTINCT robot_nodes.robot_id) as robot_count')

shared_nodes.each do |node|
  puts "Node #{node.id}: #{node.robot_count} robots"
  puts "  Content: #{node.content[0..80]}..."

  # Show which robots
  node.robot_nodes.each do |rn|
    puts "  - Robot #{rn.robot.name}: remembered #{rn.remember_count}x"
  end
end

Robot Activity Tracking

HTM automatically tracks robot activity through:

1. Robot Registry Updates

Every HTM operation updates the robot's last_active timestamp:

def update_robot_activity
  @long_term_memory.update_robot_activity(@robot_id)
end

# SQL
# UPDATE robots
# SET last_active = CURRENT_TIMESTAMP
# WHERE id = $1

2. Operations Log

Every operation is logged with robot attribution:

def add_node(key, value, ...)
  # ... add node ...

  # Log operation
  @long_term_memory.log_operation(
    operation: 'add',
    node_id: node_id,
    robot_id: @robot_id,
    details: { key: key, type: type }
  )
end
CREATE TABLE operations_log (
  id BIGSERIAL PRIMARY KEY,
  timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  operation TEXT NOT NULL,  -- add, retrieve, recall, forget, evict
  node_id BIGINT REFERENCES nodes(id),
  robot_id TEXT NOT NULL REFERENCES robots(id),
  details JSONB
);

3. Activity Queries

-- Active robots in last 24 hours
SELECT id, name, last_active
FROM robots
WHERE last_active > NOW() - INTERVAL '24 hours'
ORDER BY last_active DESC;

-- Operations by robot
SELECT r.name, COUNT(ol.id) as operation_count
FROM robots r
JOIN operations_log ol ON ol.robot_id = r.id
WHERE ol.timestamp > NOW() - INTERVAL '7 days'
GROUP BY r.name
ORDER BY operation_count DESC;

-- Most active robots by memory contributions
SELECT r.name, COUNT(n.id) as memory_count
FROM robots r
JOIN nodes n ON n.robot_id = r.id
WHERE n.created_at > NOW() - INTERVAL '30 days'
GROUP BY r.name
ORDER BY memory_count DESC;

Privacy Considerations

The hive mind architecture has important privacy implications:

Current Design: Single-User Assumption

HTM v1 assumes a single-user scenario where all robots work for the same user:

  • All robots see all memories
  • No isolation between robots
  • No access control or permissions
  • Simple, performant, easy to use

Privacy Warning

In the current design, all robots can access all memories. This is intentional for single-user scenarios but unsuitable for multi-user or multi-tenant deployments without additional security layers.

Future: Multi-Tenancy Support

For multi-user scenarios, consider these privacy enhancements:

1. Row-Level Security (RLS)

PostgreSQL's RLS can enforce tenant isolation:

-- Enable RLS on nodes table
ALTER TABLE nodes ENABLE ROW LEVEL SECURITY;

-- Policy: Users can only see their own tenant's nodes
CREATE POLICY tenant_isolation ON nodes
  FOR ALL
  TO PUBLIC
  USING (tenant_id = current_setting('app.tenant_id')::TEXT);

-- Set tenant context per connection
SET app.tenant_id = 'user-123';

2. Robot Visibility Levels

Add visibility controls to nodes:

ALTER TABLE nodes ADD COLUMN visibility TEXT DEFAULT 'shared';
-- Values: 'private' (robot-only), 'shared' (all robots), 'team' (robot group)

-- Private memory (only this robot)
htm.add_node("private_key", "sensitive data", visibility: :private)

-- Shared with specific robots
htm.add_node("team_key", "team data", visibility: { team: ['robot-a', 'robot-b'] })

3. Robot Groups/Teams

Organize robots into teams with shared memory:

CREATE TABLE robot_teams (
  id BIGSERIAL PRIMARY KEY,
  name TEXT NOT NULL,
  created_at TIMESTAMP DEFAULT NOW()
);

CREATE TABLE robot_team_members (
  robot_id TEXT REFERENCES robots(id),
  team_id BIGINT REFERENCES robot_teams(id),
  PRIMARY KEY (robot_id, team_id)
);

-- Query memories by team
SELECT n.*
FROM nodes n
JOIN robot_team_members rtm ON rtm.robot_id = n.robot_id
WHERE rtm.team_id = $1;

Performance Characteristics

Shared Long-Term Memory

Aspect Performance Notes
Concurrent reads Excellent PostgreSQL read scaling
Concurrent writes Good MVCC handles concurrent inserts
Attribution queries Fast Indexed on robot_id
Cross-robot search Fast Same as single-robot search
Registry updates Minimal overhead Simple UPDATE per operation

Per-Robot Working Memory

Aspect Performance Notes
Memory isolation O(1) No synchronization needed
Process independence Excellent No shared state
Eviction O(n log n) Per-robot, doesn't affect others
Context assembly O(n log n) Per-robot, fast

Scalability

Vertical Scaling

  • Database connections: Use connection pooling (default)
  • Robot count: Limited by database connections (~100-200 concurrent)
  • Memory size: Each robot process uses ~1-2GB RAM for working memory

Horizontal Scaling

  • Multi-process: Each robot process is independent
  • Multi-host: All hosts share PostgreSQL database
  • Read replicas: Route reads to replicas, writes to primary
  • Sharding: Partition by robot_id or tenant_id for massive scale

Code Examples

Example 1: Persistent Robot with Named Identity

# Initialize with persistent name
htm = HTM.new(
  robot_name: "Code Helper",
  working_memory_size: 128_000
)

# Add memories (linked to this robot via robot_nodes)
htm.remember("Use PostgreSQL for ACID guarantees and pgvector support")

# Robot ID is stored in database, robot_name is human-readable
puts "Robot ID: #{htm.robot_id}"
puts "Robot name: #{htm.robot_name}"

Example 2: Multi-Robot Collaboration with Deduplication

# Robot A: Architecture discussion
robot_a = HTM.new(robot_name: "Architect")
node_id = robot_a.remember(
  "PostgreSQL chosen for ACID guarantees and pgvector support",
  tags: ["architecture:database", "decision"]
)

# Robot B: Implementation (different process, accesses same LTM)
robot_b = HTM.new(robot_name: "Coder")
decisions = robot_b.recall("database decision", timeframe: "today")
# => Finds Robot A's decision automatically

# If Robot B remembers the same content, it links to existing node
same_node_id = robot_b.remember(
  "PostgreSQL chosen for ACID guarantees and pgvector support"
)
# => same_node_id == node_id (deduplication!)

robot_b.remember(
  "Implemented Database class with connection pooling",
  tags: ["implementation:database", "code:ruby"]
)

Example 3: Robot Activity Dashboard

# Get all robots and their activity
stats = []

HTM::Models::Robot.order(last_active: :desc).each do |robot|
  # Count memories via robot_nodes
  memory_count = robot.robot_nodes.count

  # Get remember statistics
  remember_stats = robot.robot_nodes.group(:node_id).count.size  # Unique nodes
  total_remembers = robot.robot_nodes.sum(:remember_count)       # Total remembers

  stats << {
    name: robot.name,
    id: robot.id,
    last_active: robot.last_active,
    unique_memories: memory_count,
    total_remembers: total_remembers
  }
end

# Display dashboard
puts "=" * 60
puts "Robot Activity Dashboard"
puts "=" * 60
stats.each do |data|
  puts "#{data[:name]} (ID: #{data[:id]})"
  puts "  Last active: #{data[:last_active]}"
  puts "  Unique memories: #{data[:unique_memories]}"
  puts "  Total remembers: #{data[:total_remembers]}"
  puts
end