 f3cbb5c6f7
			
		
	
	f3cbb5c6f7
	
	
	
		
			
			- Parameterize CORS_ORIGINS in docker-compose.swarm.yml - Add .env.example with configuration options - Create comprehensive LOCAL_DEVELOPMENT.md guide - Update README.md with environment variable documentation - Provide alternatives for local development without production domain 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
		
			
				
	
	
		
			345 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			345 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
	
	
| #!/usr/bin/env python3
 | |
| """
 | |
| Auto-Discovery Agent Registration Script for Hive
 | |
| Automatically discovers Ollama endpoints on the subnet and registers them as agents
 | |
| """
 | |
| 
 | |
| import asyncio
 | |
| import aiohttp
 | |
| import json
 | |
| import socket
 | |
| import subprocess
 | |
| import sys
 | |
| from concurrent.futures import ThreadPoolExecutor
 | |
| from typing import Dict, List, Optional, Tuple
 | |
| import time
 | |
| 
 | |
| # Configuration
 | |
| HIVE_API_URL = "https://hive.home.deepblack.cloud"
 | |
| SUBNET_BASE = "192.168.1"
 | |
| OLLAMA_PORT = 11434
 | |
| DISCOVERY_TIMEOUT = 3
 | |
| 
 | |
| class AgentDiscovery:
 | |
|     def __init__(self):
 | |
|         self.session = None
 | |
|         self.discovered_agents = []
 | |
|         
 | |
|     async def __aenter__(self):
 | |
|         self.session = aiohttp.ClientSession(
 | |
|             timeout=aiohttp.ClientTimeout(total=DISCOVERY_TIMEOUT)
 | |
|         )
 | |
|         return self
 | |
|         
 | |
|     async def __aexit__(self, exc_type, exc_val, exc_tb):
 | |
|         if self.session:
 | |
|             await self.session.close()
 | |
|     
 | |
|     def get_subnet_hosts(self) -> List[str]:
 | |
|         """Get list of potential hosts in subnet"""
 | |
|         # Get network info from cluster docs
 | |
|         known_hosts = [
 | |
|             "192.168.1.27",   # WALNUT
 | |
|             "192.168.1.72",   # ACACIA  
 | |
|             "192.168.1.113",  # IRONWOOD
 | |
|             "192.168.1.106",  # FORSTEINET
 | |
|             "192.168.1.132",  # ROSEWOOD
 | |
|         ]
 | |
|         
 | |
|         # Also scan common IP ranges
 | |
|         additional_hosts = [f"{SUBNET_BASE}.{i}" for i in range(1, 255)]
 | |
|         
 | |
|         # Combine and deduplicate
 | |
|         all_hosts = list(set(known_hosts + additional_hosts))
 | |
|         return all_hosts
 | |
|     
 | |
|     def is_port_open(self, host: str, port: int) -> bool:
 | |
|         """Check if port is open on host"""
 | |
|         try:
 | |
|             with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
 | |
|                 sock.settimeout(1)
 | |
|                 result = sock.connect_ex((host, port))
 | |
|                 return result == 0
 | |
|         except:
 | |
|             return False
 | |
|     
 | |
|     async def get_ollama_info(self, host: str) -> Optional[Dict]:
 | |
|         """Get Ollama instance information"""
 | |
|         try:
 | |
|             endpoint = f"http://{host}:{OLLAMA_PORT}"
 | |
|             
 | |
|             # Test basic connectivity
 | |
|             async with self.session.get(f"{endpoint}/api/tags") as response:
 | |
|                 if response.status != 200:
 | |
|                     return None
 | |
|                 
 | |
|                 models_data = await response.json()
 | |
|                 models = [m["name"] for m in models_data.get("models", [])]
 | |
|                 
 | |
|                 if not models:
 | |
|                     return None
 | |
|                 
 | |
|                 # Get system info if available
 | |
|                 system_info = await self.get_system_info(host)
 | |
|                 
 | |
|                 return {
 | |
|                     "host": host,
 | |
|                     "endpoint": endpoint,
 | |
|                     "models": models,
 | |
|                     "model_count": len(models),
 | |
|                     "primary_model": models[0],  # Use first model as primary
 | |
|                     "system_info": system_info
 | |
|                 }
 | |
|         except Exception as e:
 | |
|             print(f"  ❌ Error checking {host}: {e}")
 | |
|             return None
 | |
|     
 | |
|     async def get_system_info(self, host: str) -> Dict:
 | |
|         """Get system information (if available)"""
 | |
|         try:
 | |
|             # Try to get hostname via reverse DNS
 | |
|             try:
 | |
|                 hostname = socket.gethostbyaddr(host)[0]
 | |
|                 # Clean up .local suffix and use short name
 | |
|                 if hostname.endswith('.local'):
 | |
|                     hostname = hostname[:-6]
 | |
|             except:
 | |
|                 hostname = host
 | |
|             
 | |
|             # Special mapping for known hosts
 | |
|             hostname_map = {
 | |
|                 "192.168.1.135": "oak",
 | |
|                 "192.168.1.27": "walnut", 
 | |
|                 "192.168.1.72": "acacia",
 | |
|                 "192.168.1.113": "ironwood",
 | |
|                 "192.168.1.132": "rosewood",
 | |
|                 "192.168.1.106": "forsteinet"
 | |
|             }
 | |
|             
 | |
|             if host in hostname_map:
 | |
|                 hostname = hostname_map[host]
 | |
|             
 | |
|             return {
 | |
|                 "hostname": hostname,
 | |
|                 "ip": host
 | |
|             }
 | |
|         except:
 | |
|             return {"hostname": host, "ip": host}
 | |
|     
 | |
|     async def discover_agents(self) -> List[Dict]:
 | |
|         """Discover all Ollama agents on the network"""
 | |
|         print("🔍 Scanning subnet for Ollama endpoints...")
 | |
|         
 | |
|         hosts = self.get_subnet_hosts()
 | |
|         
 | |
|         # Filter hosts with open Ollama port
 | |
|         print(f"  📡 Checking {len(hosts)} potential hosts...")
 | |
|         
 | |
|         open_hosts = []
 | |
|         with ThreadPoolExecutor(max_workers=50) as executor:
 | |
|             futures = {executor.submit(self.is_port_open, host, OLLAMA_PORT): host 
 | |
|                       for host in hosts}
 | |
|             
 | |
|             for future in futures:
 | |
|                 host = futures[future]
 | |
|                 try:
 | |
|                     if future.result():
 | |
|                         open_hosts.append(host)
 | |
|                         print(f"  ✅ Found open port: {host}:{OLLAMA_PORT}")
 | |
|                 except:
 | |
|                     pass
 | |
|         
 | |
|         print(f"  📊 Found {len(open_hosts)} hosts with open Ollama ports")
 | |
|         
 | |
|         # Get detailed info for each host
 | |
|         print("  📋 Gathering agent information...")
 | |
|         
 | |
|         discovered = []
 | |
|         for host in open_hosts:
 | |
|             print(f"  🔍 Checking {host}...")
 | |
|             info = await self.get_ollama_info(host)
 | |
|             if info:
 | |
|                 discovered.append(info)
 | |
|                 print(f"  ✅ {host}: {info['model_count']} models")
 | |
|             else:
 | |
|                 print(f"  ❌ {host}: No response")
 | |
|         
 | |
|         return discovered
 | |
|     
 | |
|     def determine_agent_specialty(self, models: List[str], hostname: str) -> str:
 | |
|         """Determine agent specialty based on models and hostname using valid AgentType values"""
 | |
|         model_str = " ".join(models).lower()
 | |
|         hostname_lower = hostname.lower()
 | |
|         
 | |
|         # Check hostname patterns - map to valid Hive AgentType values
 | |
|         if "walnut" in hostname_lower:
 | |
|             return "pytorch_dev"  # Full-stack development
 | |
|         elif "acacia" in hostname_lower:
 | |
|             return "profiler"  # Infrastructure/DevOps
 | |
|         elif "ironwood" in hostname_lower:
 | |
|             return "pytorch_dev"  # Backend development  
 | |
|         elif "forsteinet" in hostname_lower:
 | |
|             return "kernel_dev"  # AI Compute
 | |
|         elif "rosewood" in hostname_lower:
 | |
|             return "tester"  # QA and Testing
 | |
|         elif "oak" in hostname_lower:
 | |
|             return "docs_writer"  # iOS/macOS Development
 | |
|         
 | |
|         # Check model patterns
 | |
|         if "starcoder" in model_str or "codegemma" in model_str:
 | |
|             return "pytorch_dev"  # Code generation
 | |
|         elif "deepseek-coder" in model_str:
 | |
|             return "pytorch_dev"  # Backend development
 | |
|         elif "deepseek-r1" in model_str:
 | |
|             return "profiler"  # Analysis and architecture
 | |
|         elif "devstral" in model_str:
 | |
|             return "tester"  # Development review
 | |
|         elif "llava" in model_str:
 | |
|             return "docs_writer"  # Vision/documentation
 | |
|         else:
 | |
|             return "pytorch_dev"  # Default to pytorch development
 | |
|     
 | |
|     def determine_capabilities(self, specialty: str) -> List[str]:
 | |
|         """Determine capabilities based on specialty"""
 | |
|         capability_map = {
 | |
|             "Senior Full-Stack Development & Architecture": [
 | |
|                 "full_stack_development", "frontend_frameworks", "backend_apis",
 | |
|                 "database_integration", "performance_optimization", "code_architecture"
 | |
|             ],
 | |
|             "Infrastructure, DevOps & System Architecture": [
 | |
|                 "infrastructure_design", "devops_automation", "system_architecture",
 | |
|                 "database_design", "security_implementation", "container_orchestration"
 | |
|             ],
 | |
|             "Backend Development & Code Analysis": [
 | |
|                 "backend_development", "api_design", "code_analysis", "debugging",
 | |
|                 "testing_frameworks", "database_optimization"
 | |
|             ],
 | |
|             "AI Compute & Processing": [
 | |
|                 "ai_model_inference", "gpu_computing", "distributed_processing",
 | |
|                 "model_optimization", "performance_tuning"
 | |
|             ],
 | |
|             "Quality Assurance, Testing & Code Review": [
 | |
|                 "quality_assurance", "automated_testing", "code_review",
 | |
|                 "test_automation", "performance_testing"
 | |
|             ],
 | |
|             "iOS/macOS Development & Apple Ecosystem": [
 | |
|                 "ios_development", "macos_development", "swift_programming",
 | |
|                 "objective_c_development", "xcode_automation", "app_store_deployment",
 | |
|                 "swiftui_development", "uikit_development", "apple_framework_integration"
 | |
|             ]
 | |
|         }
 | |
|         
 | |
|         return capability_map.get(specialty, ["general_development", "code_assistance"])
 | |
|     
 | |
|     async def register_agent(self, agent_info: Dict) -> bool:
 | |
|         """Register a discovered agent with Hive"""
 | |
|         try:
 | |
|             hostname = agent_info["system_info"]["hostname"]
 | |
|             specialty = self.determine_agent_specialty(agent_info["models"], hostname)
 | |
|             capabilities = self.determine_capabilities(specialty)
 | |
|             
 | |
|             agent_data = {
 | |
|                 "id": hostname.lower().replace(".", "_"),
 | |
|                 "name": f"{hostname} Ollama Agent",
 | |
|                 "endpoint": agent_info["endpoint"],
 | |
|                 "model": agent_info["primary_model"],
 | |
|                 "specialty": specialty,
 | |
|                 "specialization": specialty,  # For compatibility
 | |
|                 "capabilities": capabilities,
 | |
|                 "available_models": agent_info["models"],
 | |
|                 "model_count": agent_info["model_count"],
 | |
|                 "hostname": hostname,
 | |
|                 "ip_address": agent_info["host"],
 | |
|                 "status": "available",
 | |
|                 "current_tasks": 0,
 | |
|                 "max_concurrent": 3,
 | |
|                 "agent_type": "ollama",
 | |
|                 "discovered_at": time.time()
 | |
|             }
 | |
|             
 | |
|             async with self.session.post(
 | |
|                 f"{HIVE_API_URL}/api/agents",
 | |
|                 json=agent_data,
 | |
|                 headers={"Content-Type": "application/json"}
 | |
|             ) as response:
 | |
|                 if response.status == 200:
 | |
|                     result = await response.json()
 | |
|                     print(f"  ✅ Registered {hostname} as {specialty}")
 | |
|                     return True
 | |
|                 else:
 | |
|                     text = await response.text()
 | |
|                     print(f"  ❌ Failed to register {hostname}: {response.status} - {text}")
 | |
|                     return False
 | |
|                     
 | |
|         except Exception as e:
 | |
|             print(f"  ❌ Error registering {agent_info['host']}: {e}")
 | |
|             return False
 | |
|     
 | |
|     async def test_hive_connection(self) -> bool:
 | |
|         """Test connection to Hive API"""
 | |
|         try:
 | |
|             async with self.session.get(f"{HIVE_API_URL}/health") as response:
 | |
|                 if response.status == 200:
 | |
|                     print("✅ Connected to Hive API")
 | |
|                     return True
 | |
|                 else:
 | |
|                     print(f"❌ Hive API returned status {response.status}")
 | |
|                     return False
 | |
|         except Exception as e:
 | |
|             print(f"❌ Failed to connect to Hive API: {e}")
 | |
|             return False
 | |
| 
 | |
| async def main():
 | |
|     """Main discovery and registration process"""
 | |
|     print("🐝 Hive Agent Auto-Discovery Script")
 | |
|     print("=" * 50)
 | |
|     
 | |
|     async with AgentDiscovery() as discovery:
 | |
|         # Test Hive connection
 | |
|         if not await discovery.test_hive_connection():
 | |
|             print("❌ Cannot connect to Hive API. Make sure Hive is running.")
 | |
|             sys.exit(1)
 | |
|         
 | |
|         # Discover agents
 | |
|         discovered_agents = await discovery.discover_agents()
 | |
|         
 | |
|         if not discovered_agents:
 | |
|             print("❌ No Ollama agents discovered on the network")
 | |
|             sys.exit(1)
 | |
|         
 | |
|         print(f"\n📊 Discovered {len(discovered_agents)} agents:")
 | |
|         for agent in discovered_agents:
 | |
|             hostname = agent["system_info"]["hostname"]
 | |
|             print(f"  • {hostname} ({agent['host']}) - {agent['model_count']} models")
 | |
|         
 | |
|         # Register agents
 | |
|         print("\n🔄 Registering discovered agents...")
 | |
|         
 | |
|         successful = 0
 | |
|         failed = 0
 | |
|         
 | |
|         for agent_info in discovered_agents:
 | |
|             hostname = agent_info["system_info"]["hostname"]
 | |
|             print(f"\n📡 Registering {hostname}...")
 | |
|             
 | |
|             if await discovery.register_agent(agent_info):
 | |
|                 successful += 1
 | |
|             else:
 | |
|                 failed += 1
 | |
|         
 | |
|         # Summary
 | |
|         print("\n" + "=" * 50)
 | |
|         print(f"📊 Discovery & Registration Summary:")
 | |
|         print(f"  🔍 Discovered: {len(discovered_agents)}")
 | |
|         print(f"  ✅ Registered: {successful}")
 | |
|         print(f"  ❌ Failed: {failed}")
 | |
|         
 | |
|         if successful > 0:
 | |
|             print(f"\n🎉 Successfully registered {successful} agents!")
 | |
|             print("🔗 Check status: curl http://localhost:8087/api/status")
 | |
|         else:
 | |
|             print("\n💔 No agents were successfully registered.")
 | |
|             sys.exit(1)
 | |
| 
 | |
| if __name__ == "__main__":
 | |
|     asyncio.run(main()) |