bin/ 230 CLI tools (ask-*, br-*, agent-*, roadid, carpool) scripts/ 99 automation scripts fleet/ Node configs and deployment workers/ Cloudflare Worker sources (roadpay, road-search, squad webhooks) roadc/ RoadC programming language roadnet/ Mesh network (5 APs, WireGuard) operator/ Memory system scripts config/ System configs dotfiles/ Shell configs docs/ Documentation BlackRoad OS — Pave Tomorrow. RoadChain-SHA2048: d1a24f55318d338b RoadChain-Identity: alexa@sovereign RoadChain-Full: d1a24f55318d338b24b60bad7be39286379c76ae5470817482100cb0ddbbcb97e147d07ac7243da0a9f0363e4e5c833d612b9c0df3a3cd20802465420278ef74875a5b77f55af6fe42a931b8b635b3d0d0b6bde9abf33dc42eea52bc03c951406d8cbe49f1a3d29b26a94dade05e9477f34a7d4d4c6ec4005c3c2ac54e73a68440c512c8e83fd9b1fe234750b898ef8f4032c23db173961fe225e67a0432b5293a9714f76c5c57ed5fdf35b9fb40fd73c03ebf88b7253c6a0575f5afb6a6b49b3bda310602fb1ef676859962dad2aebbb2875814b30eee0a8ba195e482d4cbc91d8819e7f38f6db53e8063401649c77bb994371473cabfb917fb53e8cbe73d60
798 lines
30 KiB
Python
Executable File
798 lines
30 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""
|
|
CarPool — Pick up your agent. Ride the BlackRoad.
|
|
|
|
Agent discovery, matching, and dispatch system for the BlackRoad mesh.
|
|
Every AI agent on the fleet gets a RoadID, advertises its capabilities,
|
|
and riders (prompts/requests) get matched to the best available agent.
|
|
|
|
Think of it like rideshare but for AI:
|
|
- Agents are DRIVERS — they have skills, a location (node), capacity
|
|
- Prompts are RIDERS — they need a specific kind of help
|
|
- CarPool MATCHES riders to drivers based on capability, proximity, load
|
|
|
|
Architecture:
|
|
- Agent Registry: JSON file per node, synced across mesh
|
|
- Capability Tags: what each agent can do (chat, code, vision, tts, etc.)
|
|
- Load Balancing: route to least-loaded capable agent
|
|
- RoadID Integration: every agent, ride, and match gets a RoadID
|
|
|
|
Usage:
|
|
carpool register cece llama3 --caps chat,code --model llama3.2
|
|
carpool register oct qwen --caps chat,code,math --model qwen2.5-coder
|
|
carpool pickup "help me write a Python script"
|
|
carpool pickup "generate an image of a sunset" --prefer cece
|
|
carpool status
|
|
carpool riders # show pending requests
|
|
carpool fleet # show all agents across mesh
|
|
carpool explain <ride-id> # trace a match decision
|
|
|
|
Python API:
|
|
from carpool import CarPool
|
|
pool = CarPool()
|
|
pool.register_agent("cece", "llama3", caps=["chat", "code"], model="llama3.2")
|
|
match = pool.pickup("help me debug this function")
|
|
print(match) # → matched agent, node, ride ID, reasoning
|
|
"""
|
|
|
|
import os
|
|
import sys
|
|
import json
|
|
import time
|
|
import re
|
|
import hashlib
|
|
from pathlib import Path
|
|
from datetime import datetime, timezone
|
|
from typing import Optional
|
|
|
|
# Import RoadID from same directory
|
|
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
|
from roadid import RoadID, NODES, to_base36
|
|
|
|
# === AGENT CAPABILITIES ===
|
|
CAPABILITIES = {
|
|
"chat": "General conversation and Q&A",
|
|
"code": "Code generation, debugging, review",
|
|
"math": "Mathematical reasoning and computation",
|
|
"vision": "Image understanding and analysis",
|
|
"tts": "Text-to-speech synthesis",
|
|
"stt": "Speech-to-text transcription",
|
|
"img": "Image generation",
|
|
"embed": "Vector embeddings",
|
|
"rag": "Retrieval-augmented generation",
|
|
"tool": "Tool use and function calling",
|
|
"agent": "Autonomous agent tasks",
|
|
"dns": "DNS resolution and management",
|
|
"git": "Git operations and code management",
|
|
"docker": "Container management",
|
|
"monitor": "System monitoring and health checks",
|
|
"hailo": "Hailo-8 accelerated inference (26 TOPS)",
|
|
"mesh": "Browser mesh node coordination",
|
|
"search": "Web search and retrieval",
|
|
"memory": "Long-term memory and recall",
|
|
}
|
|
|
|
# Keyword → capability mapping for natural language matching
|
|
KEYWORD_MAP = {
|
|
# chat
|
|
"talk": "chat", "conversation": "chat", "discuss": "chat", "ask": "chat",
|
|
"question": "chat", "answer": "chat", "help": "chat", "explain": "chat",
|
|
# code
|
|
"code": "code", "program": "code", "script": "code", "debug": "code",
|
|
"function": "code", "class": "code", "python": "code", "javascript": "code",
|
|
"rust": "code", "go": "code", "typescript": "code", "fix": "code",
|
|
"refactor": "code", "api": "code", "bug": "code", "error": "code",
|
|
# math
|
|
"math": "math", "calculate": "math", "equation": "math", "algebra": "math",
|
|
"statistics": "math", "probability": "math", "proof": "math", "formula": "math",
|
|
# vision
|
|
"image": "vision", "picture": "vision", "photo": "vision", "screenshot": "vision",
|
|
"see": "vision", "look": "vision", "visual": "vision", "ocr": "vision",
|
|
# tts
|
|
"speak": "tts", "voice": "tts", "read aloud": "tts", "say": "tts",
|
|
"pronounce": "tts", "audio out": "tts", "narrate": "tts",
|
|
# stt
|
|
"transcribe": "stt", "listen": "stt", "dictate": "stt", "audio in": "stt",
|
|
# img gen
|
|
"generate image": "img", "draw": "img", "create image": "img",
|
|
"illustration": "img", "render": "img",
|
|
# embed
|
|
"embedding": "embed", "vector": "embed", "similarity": "embed", "semantic": "embed",
|
|
# rag
|
|
"search docs": "rag", "knowledge base": "rag", "retrieve": "rag", "lookup": "rag",
|
|
# agent
|
|
"autonomous": "agent", "automate": "agent", "workflow": "agent", "pipeline": "agent",
|
|
"task": "agent", "multi-step": "agent", "plan": "agent",
|
|
# git
|
|
"git": "git", "commit": "git", "branch": "git", "merge": "git", "repo": "git",
|
|
# docker
|
|
"container": "docker", "docker": "docker", "deploy": "docker", "swarm": "docker",
|
|
# search
|
|
"search": "search", "find": "search", "google": "search", "web": "search",
|
|
# memory
|
|
"remember": "memory", "recall": "memory", "memory": "memory", "history": "memory",
|
|
}
|
|
|
|
# === PATHS ===
|
|
CARPOOL_DIR = Path.home() / ".blackroad" / "carpool"
|
|
REGISTRY_FILE = CARPOOL_DIR / "agents.json"
|
|
RIDES_FILE = CARPOOL_DIR / "rides.json"
|
|
STATS_FILE = CARPOOL_DIR / "stats.json"
|
|
|
|
|
|
class Agent:
|
|
"""An AI agent available for pickup on the BlackRoad mesh."""
|
|
|
|
def __init__(self, road_id: str, node: str, name: str, caps: list,
|
|
model: str = None, endpoint: str = None, max_concurrent: int = 4,
|
|
priority: int = 5, meta: dict = None):
|
|
self.road_id = road_id
|
|
self.node = node
|
|
self.name = name
|
|
self.caps = caps
|
|
self.model = model
|
|
self.endpoint = endpoint
|
|
self.max_concurrent = max_concurrent
|
|
self.priority = priority # 1=highest, 10=lowest
|
|
self.meta = meta or {}
|
|
self.active_rides = 0
|
|
self.total_rides = 0
|
|
self.registered_at = time.time()
|
|
self.last_seen = time.time()
|
|
self.status = "available" # available, busy, offline
|
|
|
|
def to_dict(self) -> dict:
|
|
return {
|
|
"road_id": self.road_id,
|
|
"node": self.node,
|
|
"name": self.name,
|
|
"caps": self.caps,
|
|
"model": self.model,
|
|
"endpoint": self.endpoint,
|
|
"max_concurrent": self.max_concurrent,
|
|
"priority": self.priority,
|
|
"meta": self.meta,
|
|
"active_rides": self.active_rides,
|
|
"total_rides": self.total_rides,
|
|
"registered_at": self.registered_at,
|
|
"last_seen": self.last_seen,
|
|
"status": self.status,
|
|
}
|
|
|
|
@classmethod
|
|
def from_dict(cls, d: dict) -> "Agent":
|
|
a = cls(
|
|
road_id=d["road_id"], node=d["node"], name=d["name"],
|
|
caps=d["caps"], model=d.get("model"), endpoint=d.get("endpoint"),
|
|
max_concurrent=d.get("max_concurrent", 4),
|
|
priority=d.get("priority", 5), meta=d.get("meta", {}),
|
|
)
|
|
a.active_rides = d.get("active_rides", 0)
|
|
a.total_rides = d.get("total_rides", 0)
|
|
a.registered_at = d.get("registered_at", time.time())
|
|
a.last_seen = d.get("last_seen", time.time())
|
|
a.status = d.get("status", "available")
|
|
return a
|
|
|
|
@property
|
|
def load(self) -> float:
|
|
"""0.0 = idle, 1.0 = at capacity."""
|
|
if self.max_concurrent == 0:
|
|
return 1.0
|
|
return self.active_rides / self.max_concurrent
|
|
|
|
@property
|
|
def available(self) -> bool:
|
|
return self.status == "available" and self.load < 1.0
|
|
|
|
|
|
class Ride:
|
|
"""A request (rider) looking for an agent (driver)."""
|
|
|
|
def __init__(self, road_id: str, prompt: str, needed_caps: list,
|
|
preferred_node: str = None, matched_agent: str = None,
|
|
match_reason: str = None):
|
|
self.road_id = road_id
|
|
self.prompt = prompt
|
|
self.needed_caps = needed_caps
|
|
self.preferred_node = preferred_node
|
|
self.matched_agent = matched_agent
|
|
self.match_reason = match_reason
|
|
self.created_at = time.time()
|
|
self.matched_at = None
|
|
self.completed_at = None
|
|
self.status = "waiting" # waiting, matched, riding, completed, failed
|
|
|
|
def to_dict(self) -> dict:
|
|
return {
|
|
"road_id": self.road_id,
|
|
"prompt": self.prompt,
|
|
"needed_caps": self.needed_caps,
|
|
"preferred_node": self.preferred_node,
|
|
"matched_agent": self.matched_agent,
|
|
"match_reason": self.match_reason,
|
|
"created_at": self.created_at,
|
|
"matched_at": self.matched_at,
|
|
"completed_at": self.completed_at,
|
|
"status": self.status,
|
|
}
|
|
|
|
@classmethod
|
|
def from_dict(cls, d: dict) -> "Ride":
|
|
r = cls(
|
|
road_id=d["road_id"], prompt=d["prompt"],
|
|
needed_caps=d["needed_caps"],
|
|
preferred_node=d.get("preferred_node"),
|
|
matched_agent=d.get("matched_agent"),
|
|
match_reason=d.get("match_reason"),
|
|
)
|
|
r.created_at = d.get("created_at", time.time())
|
|
r.matched_at = d.get("matched_at")
|
|
r.completed_at = d.get("completed_at")
|
|
r.status = d.get("status", "waiting")
|
|
return r
|
|
|
|
|
|
class CarPool:
|
|
"""
|
|
Pick up your agent. Ride the BlackRoad.
|
|
|
|
Agent discovery, matching, and dispatch across the mesh.
|
|
"""
|
|
|
|
def __init__(self):
|
|
self.rid = RoadID()
|
|
CARPOOL_DIR.mkdir(parents=True, exist_ok=True)
|
|
self.agents = self._load_agents()
|
|
self.rides = self._load_rides()
|
|
|
|
# === PERSISTENCE ===
|
|
|
|
def _load_agents(self) -> dict:
|
|
if REGISTRY_FILE.exists():
|
|
data = json.loads(REGISTRY_FILE.read_text())
|
|
return {k: Agent.from_dict(v) for k, v in data.items()}
|
|
return {}
|
|
|
|
def _save_agents(self):
|
|
data = {k: v.to_dict() for k, v in self.agents.items()}
|
|
REGISTRY_FILE.write_text(json.dumps(data, indent=2))
|
|
|
|
def _load_rides(self) -> dict:
|
|
if RIDES_FILE.exists():
|
|
data = json.loads(RIDES_FILE.read_text())
|
|
return {k: Ride.from_dict(v) for k, v in data.items()}
|
|
return {}
|
|
|
|
def _save_rides(self):
|
|
data = {k: v.to_dict() for k, v in self.rides.items()}
|
|
RIDES_FILE.write_text(json.dumps(data, indent=2))
|
|
|
|
# === AGENT REGISTRATION ===
|
|
|
|
def register_agent(self, node: str, name: str, caps: list, model: str = None,
|
|
endpoint: str = None, max_concurrent: int = 4,
|
|
priority: int = 5, meta: dict = None) -> Agent:
|
|
"""Register a new agent on the mesh."""
|
|
road_id = self.rid.generate(node, "node")
|
|
agent = Agent(
|
|
road_id=road_id, node=node, name=name, caps=caps,
|
|
model=model, endpoint=endpoint, max_concurrent=max_concurrent,
|
|
priority=priority, meta=meta,
|
|
)
|
|
self.agents[road_id] = agent
|
|
self._save_agents()
|
|
return agent
|
|
|
|
def deregister_agent(self, road_id: str) -> bool:
|
|
"""Remove an agent from the pool."""
|
|
if road_id in self.agents:
|
|
del self.agents[road_id]
|
|
self._save_agents()
|
|
return True
|
|
return False
|
|
|
|
def heartbeat(self, road_id: str) -> bool:
|
|
"""Update an agent's last-seen timestamp."""
|
|
if road_id in self.agents:
|
|
self.agents[road_id].last_seen = time.time()
|
|
self._save_agents()
|
|
return True
|
|
return False
|
|
|
|
# === CAPABILITY DETECTION ===
|
|
|
|
def detect_caps(self, prompt: str) -> list:
|
|
"""
|
|
Analyze a natural language prompt and detect needed capabilities.
|
|
This is the "RoadSide assistance" — parsing what you need.
|
|
"""
|
|
prompt_lower = prompt.lower()
|
|
detected = set()
|
|
|
|
# Direct keyword matching
|
|
for keyword, cap in KEYWORD_MAP.items():
|
|
if keyword in prompt_lower:
|
|
detected.add(cap)
|
|
|
|
# If nothing detected, default to chat
|
|
if not detected:
|
|
detected.add("chat")
|
|
|
|
return sorted(detected)
|
|
|
|
# === MATCHING ENGINE ===
|
|
|
|
def pickup(self, prompt: str, preferred_node: str = None,
|
|
required_caps: list = None) -> dict:
|
|
"""
|
|
Pick up an agent! Match a prompt to the best available agent.
|
|
|
|
Scoring:
|
|
- Capability match (must have ALL needed caps)
|
|
- Load (prefer less busy agents)
|
|
- Priority (lower number = higher priority)
|
|
- Node preference (bonus for preferred node)
|
|
- Proximity (same-subnet bonus)
|
|
|
|
Returns match dict with agent info, ride ID, and reasoning.
|
|
"""
|
|
# Detect what the rider needs
|
|
needed = required_caps or self.detect_caps(prompt)
|
|
|
|
# Create the ride
|
|
ride_id = self.rid.generate(self.rid.default_node, "req")
|
|
ride = Ride(
|
|
road_id=ride_id, prompt=prompt, needed_caps=needed,
|
|
preferred_node=preferred_node,
|
|
)
|
|
|
|
# Find candidates
|
|
candidates = []
|
|
for agent in self.agents.values():
|
|
if not agent.available:
|
|
continue
|
|
|
|
# Must have ALL needed capabilities
|
|
if not all(cap in agent.caps for cap in needed):
|
|
continue
|
|
|
|
# Score the match
|
|
score = self._score_match(agent, needed, preferred_node)
|
|
candidates.append((score, agent))
|
|
|
|
# Sort by score (highest first)
|
|
candidates.sort(key=lambda x: x[0], reverse=True)
|
|
|
|
if not candidates:
|
|
ride.status = "failed"
|
|
ride.match_reason = f"No available agent with caps: {', '.join(needed)}"
|
|
self.rides[ride_id] = ride
|
|
self._save_rides()
|
|
return {
|
|
"status": "no_match",
|
|
"ride_id": ride_id,
|
|
"needed_caps": needed,
|
|
"reason": ride.match_reason,
|
|
"available_agents": len([a for a in self.agents.values() if a.available]),
|
|
"total_agents": len(self.agents),
|
|
}
|
|
|
|
# Match to best candidate
|
|
best_score, best_agent = candidates[0]
|
|
ride.matched_agent = best_agent.road_id
|
|
ride.matched_at = time.time()
|
|
ride.status = "matched"
|
|
|
|
# Build reasoning
|
|
reasons = []
|
|
cap_match = len([c for c in needed if c in best_agent.caps])
|
|
reasons.append(f"caps: {cap_match}/{len(needed)} needed")
|
|
reasons.append(f"load: {best_agent.load:.0%}")
|
|
reasons.append(f"priority: {best_agent.priority}/10")
|
|
if preferred_node and best_agent.node == preferred_node:
|
|
reasons.append("preferred node match")
|
|
ride.match_reason = " | ".join(reasons)
|
|
|
|
# Update state
|
|
best_agent.active_rides += 1
|
|
best_agent.total_rides += 1
|
|
if best_agent.load >= 1.0:
|
|
best_agent.status = "busy"
|
|
|
|
self.rides[ride_id] = ride
|
|
self._save_rides()
|
|
self._save_agents()
|
|
|
|
# Decode agent's RoadID for full info
|
|
agent_info = self.rid.decode(best_agent.road_id)
|
|
|
|
return {
|
|
"status": "matched",
|
|
"ride_id": ride_id,
|
|
"agent_id": best_agent.road_id,
|
|
"agent_name": best_agent.name,
|
|
"agent_model": best_agent.model,
|
|
"agent_node": best_agent.node,
|
|
"agent_ip": agent_info.get("node_ip"),
|
|
"agent_endpoint": best_agent.endpoint,
|
|
"needed_caps": needed,
|
|
"agent_caps": best_agent.caps,
|
|
"score": round(best_score, 2),
|
|
"reason": ride.match_reason,
|
|
"alternatives": len(candidates) - 1,
|
|
}
|
|
|
|
def _score_match(self, agent: Agent, needed_caps: list,
|
|
preferred_node: str = None) -> float:
|
|
"""Score an agent for a ride. Higher = better match."""
|
|
score = 0.0
|
|
|
|
# Capability coverage (0-40 points)
|
|
# Bonus for having extra caps beyond what's needed
|
|
extra_caps = len(set(agent.caps) - set(needed_caps))
|
|
score += 40 + (extra_caps * 2) # More versatile = slight bonus
|
|
|
|
# Load (0-30 points, less loaded = more points)
|
|
score += (1.0 - agent.load) * 30
|
|
|
|
# Priority (0-20 points)
|
|
score += (10 - agent.priority) * 2
|
|
|
|
# Node preference (0-10 bonus)
|
|
if preferred_node and agent.node == preferred_node:
|
|
score += 10
|
|
|
|
return score
|
|
|
|
def complete_ride(self, ride_id: str) -> bool:
|
|
"""Mark a ride as completed, free up the agent."""
|
|
if ride_id not in self.rides:
|
|
return False
|
|
|
|
ride = self.rides[ride_id]
|
|
ride.completed_at = time.time()
|
|
ride.status = "completed"
|
|
|
|
if ride.matched_agent and ride.matched_agent in self.agents:
|
|
agent = self.agents[ride.matched_agent]
|
|
agent.active_rides = max(0, agent.active_rides - 1)
|
|
if agent.load < 1.0:
|
|
agent.status = "available"
|
|
|
|
self._save_rides()
|
|
self._save_agents()
|
|
return True
|
|
|
|
# === STATUS & DISPLAY ===
|
|
|
|
def fleet_status(self) -> str:
|
|
"""Show all agents across the mesh."""
|
|
if not self.agents:
|
|
return "No agents registered. Use: carpool register <node> <name> --caps chat,code"
|
|
|
|
lines = []
|
|
header = f"{'ID':<28} {'Node':<7} {'Name':<12} {'Model':<20} {'Caps':<25} {'Load':>6} {'Status':<10}"
|
|
lines.append(header)
|
|
lines.append("-" * len(header))
|
|
|
|
for agent in sorted(self.agents.values(), key=lambda a: (a.node, a.name)):
|
|
caps_str = ",".join(agent.caps[:4])
|
|
if len(agent.caps) > 4:
|
|
caps_str += f"+{len(agent.caps)-4}"
|
|
load_str = f"{agent.load:.0%}" if agent.max_concurrent > 0 else "N/A"
|
|
lines.append(
|
|
f"{agent.road_id:<28} {agent.node:<7} {agent.name:<12} "
|
|
f"{(agent.model or '-'):<20} {caps_str:<25} {load_str:>6} {agent.status:<10}"
|
|
)
|
|
|
|
lines.append(f"\n {len(self.agents)} agents | "
|
|
f"{sum(1 for a in self.agents.values() if a.available)} available | "
|
|
f"{sum(a.active_rides for a in self.agents.values())} active rides")
|
|
return "\n".join(lines)
|
|
|
|
def riders_status(self) -> str:
|
|
"""Show recent rides."""
|
|
if not self.rides:
|
|
return "No rides yet. Use: carpool pickup \"your prompt here\""
|
|
|
|
lines = []
|
|
header = f"{'Ride ID':<28} {'Status':<10} {'Caps':<20} {'Agent':<28} {'Age':>6}"
|
|
lines.append(header)
|
|
lines.append("-" * len(header))
|
|
|
|
recent = sorted(self.rides.values(), key=lambda r: r.created_at, reverse=True)[:20]
|
|
for ride in recent:
|
|
caps_str = ",".join(ride.needed_caps[:3])
|
|
age = time.time() - ride.created_at
|
|
age_str = f"{int(age)}s" if age < 60 else f"{int(age/60)}m"
|
|
lines.append(
|
|
f"{ride.road_id:<28} {ride.status:<10} {caps_str:<20} "
|
|
f"{(ride.matched_agent or 'waiting'):<28} {age_str:>6}"
|
|
)
|
|
|
|
return "\n".join(lines)
|
|
|
|
def explain_ride(self, ride_id: str) -> str:
|
|
"""Explain the full match decision for a ride."""
|
|
ride = self.rides.get(ride_id)
|
|
if not ride:
|
|
return f"Ride not found: {ride_id}"
|
|
|
|
lines = [
|
|
f" Ride: {ride.road_id}",
|
|
f" Prompt: {ride.prompt[:80]}{'...' if len(ride.prompt) > 80 else ''}",
|
|
f" Needed: {', '.join(ride.needed_caps)}",
|
|
f" Status: {ride.status}",
|
|
]
|
|
|
|
if ride.preferred_node:
|
|
lines.append(f" Prefer: {ride.preferred_node}")
|
|
|
|
if ride.matched_agent:
|
|
agent = self.agents.get(ride.matched_agent)
|
|
lines.append(f" Agent: {ride.matched_agent}")
|
|
if agent:
|
|
lines.append(f" Driver: {agent.name} ({agent.model}) on {agent.node}")
|
|
lines.append(f" Caps: {', '.join(agent.caps)}")
|
|
lines.append(f" Reason: {ride.match_reason}")
|
|
|
|
if ride.matched_at:
|
|
match_ms = (ride.matched_at - ride.created_at) * 1000
|
|
lines.append(f" Match in: {match_ms:.1f}ms")
|
|
|
|
if ride.completed_at:
|
|
duration = ride.completed_at - ride.created_at
|
|
lines.append(f" Duration: {duration:.1f}s")
|
|
|
|
return "\n".join(lines)
|
|
|
|
def seed_fleet(self):
|
|
"""
|
|
Seed the registry with known BlackRoad fleet agents.
|
|
Based on actual deployed services from infrastructure.
|
|
"""
|
|
agents = [
|
|
# Cecilia — CECE API, TTS, Hailo-8, 16 Ollama models
|
|
("cece", "cece-api", ["chat", "code", "agent", "memory"],
|
|
"custom-cece", "http://192.168.4.96:11434", 4, 3,
|
|
{"desc": "CECE — BlackRoad's core entity"}),
|
|
("cece", "llama3", ["chat", "code", "math", "tool"],
|
|
"llama3.2:latest", "http://192.168.4.96:11434", 2, 5, {}),
|
|
("cece", "deepseek", ["code", "math"],
|
|
"deepseek-r1:8b", "http://192.168.4.96:11434", 2, 4, {}),
|
|
("cece", "cece-tts", ["tts"],
|
|
"piper", "http://192.168.4.96:5500", 4, 2,
|
|
{"desc": "TTS via Piper on Cecilia"}),
|
|
("cece", "hailo-cece", ["vision", "hailo"],
|
|
"hailo-8", None, 8, 1,
|
|
{"tops": 26, "serial": "HLLWM2B233704667"}),
|
|
|
|
# Octavia — Gitea, NVMe, Hailo-8, Docker Swarm
|
|
("oct", "hailo-oct", ["vision", "hailo"],
|
|
"hailo-8", None, 8, 1,
|
|
{"tops": 26, "serial": "HLLWM2B233704606"}),
|
|
("oct", "gitea", ["git"],
|
|
None, "http://192.168.4.100:3100", 10, 3,
|
|
{"repos": 207}),
|
|
("oct", "ollama-oct", ["chat", "code"],
|
|
"ollama", "http://192.168.4.100:11434", 2, 6, {}),
|
|
|
|
# Alice — Gateway, Pi-hole, DNS
|
|
("alice", "pihole", ["dns"],
|
|
None, "http://192.168.4.49:80", 100, 2,
|
|
{"desc": "Pi-hole DNS for the fleet"}),
|
|
("alice", "gateway", ["dns", "monitor"],
|
|
None, "http://192.168.4.49:80", 50, 1,
|
|
{"desc": "Main gateway — 65+ domains"}),
|
|
|
|
# Lucidia — Web apps, Actions runner
|
|
("luci", "luci-api", ["chat", "code", "agent"],
|
|
"fastapi", "http://192.168.4.38:8000", 4, 5,
|
|
{"desc": "Lucidia FastAPI"}),
|
|
|
|
# Anastasia — WG hub, Headscale
|
|
("ana", "headscale", ["mesh", "monitor"],
|
|
None, None, 20, 3,
|
|
{"desc": "WireGuard hub + Headscale coordinator"}),
|
|
|
|
# Mac — Command center
|
|
("mac", "claude", ["chat", "code", "math", "tool", "agent", "vision"],
|
|
"claude-opus-4-6", None, 1, 1,
|
|
{"desc": "Claude Code on Mac — most capable"}),
|
|
|
|
# Mesh — Browser nodes (template)
|
|
("mesh", "browser", ["chat", "embed", "mesh"],
|
|
"webgpu", None, 1, 8,
|
|
{"desc": "Ephemeral browser mesh node (WebGPU+WASM)"}),
|
|
]
|
|
|
|
for node, name, caps, model, endpoint, max_c, pri, meta in agents:
|
|
self.register_agent(node, name, caps, model, endpoint, max_c, pri, meta)
|
|
|
|
return len(agents)
|
|
|
|
|
|
# === CLI ===
|
|
|
|
PINK = '\033[38;5;205m'
|
|
AMBER = '\033[38;5;214m'
|
|
BLUE = '\033[38;5;69m'
|
|
VIOLET = '\033[38;5;135m'
|
|
GREEN = '\033[38;5;82m'
|
|
RED = '\033[38;5;196m'
|
|
DIM = '\033[2m'
|
|
BOLD = '\033[1m'
|
|
RESET = '\033[0m'
|
|
|
|
|
|
def print_banner():
|
|
print(f"""{PINK}
|
|
╔═══════════════════════════════════════╗
|
|
║ {AMBER}🚗 CarPool{PINK} — Pick Up Your Agent ║
|
|
║ {DIM}Ride the BlackRoad{RESET}{PINK} ║
|
|
╚═══════════════════════════════════════╝{RESET}
|
|
""")
|
|
|
|
|
|
def main():
|
|
import argparse
|
|
|
|
parser = argparse.ArgumentParser(
|
|
description="CarPool — Pick up your agent. Ride the BlackRoad.",
|
|
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
epilog="""
|
|
Examples:
|
|
carpool seed # Populate with known fleet agents
|
|
carpool register cece llama3 --caps chat,code --model llama3.2
|
|
carpool pickup "help me write a script" # Auto-match to best agent
|
|
carpool pickup "run inference" --prefer cece
|
|
carpool fleet # Show all agents
|
|
carpool riders # Show rides
|
|
carpool explain <ride-id> # Trace match decision
|
|
carpool detect "transcribe this audio" # Show detected capabilities
|
|
"""
|
|
)
|
|
sub = parser.add_subparsers(dest="command")
|
|
|
|
# seed
|
|
sub.add_parser("seed", help="Seed registry with known fleet agents")
|
|
|
|
# register
|
|
reg = sub.add_parser("register", aliases=["reg"], help="Register a new agent")
|
|
reg.add_argument("node", help="Node name")
|
|
reg.add_argument("name", help="Agent name")
|
|
reg.add_argument("--caps", required=True, help="Comma-separated capabilities")
|
|
reg.add_argument("--model", help="Model name")
|
|
reg.add_argument("--endpoint", help="API endpoint URL")
|
|
reg.add_argument("--max-concurrent", type=int, default=4)
|
|
reg.add_argument("--priority", type=int, default=5, help="1=highest, 10=lowest")
|
|
|
|
# deregister
|
|
dereg = sub.add_parser("deregister", aliases=["dereg", "rm"], help="Remove an agent")
|
|
dereg.add_argument("id", help="Agent RoadID")
|
|
|
|
# pickup
|
|
pu = sub.add_parser("pickup", aliases=["pu", "ride"], help="Match a prompt to an agent")
|
|
pu.add_argument("prompt", help="What you need help with")
|
|
pu.add_argument("--prefer", help="Preferred node")
|
|
pu.add_argument("--caps", help="Override detected caps (comma-separated)")
|
|
|
|
# complete
|
|
comp = sub.add_parser("complete", aliases=["done"], help="Mark a ride as completed")
|
|
comp.add_argument("ride_id", help="Ride ID to complete")
|
|
|
|
# detect
|
|
det = sub.add_parser("detect", help="Show detected capabilities for a prompt")
|
|
det.add_argument("prompt", help="Prompt to analyze")
|
|
|
|
# fleet
|
|
sub.add_parser("fleet", aliases=["ls", "list"], help="Show all agents")
|
|
|
|
# riders
|
|
sub.add_parser("riders", aliases=["rides"], help="Show recent rides")
|
|
|
|
# explain
|
|
exp = sub.add_parser("explain", aliases=["x"], help="Explain a ride match")
|
|
exp.add_argument("ride_id", help="Ride ID")
|
|
|
|
# status
|
|
sub.add_parser("status", aliases=["st"], help="Fleet overview")
|
|
|
|
# reset
|
|
sub.add_parser("reset", help="Clear all agents and rides")
|
|
|
|
args = parser.parse_args()
|
|
|
|
if not args.command:
|
|
print_banner()
|
|
parser.print_help()
|
|
sys.exit(0)
|
|
|
|
pool = CarPool()
|
|
|
|
if args.command == "seed":
|
|
count = pool.seed_fleet()
|
|
print(f"{GREEN}Seeded {count} agents from fleet inventory.{RESET}")
|
|
print(pool.fleet_status())
|
|
|
|
elif args.command in ("register", "reg"):
|
|
caps = [c.strip() for c in args.caps.split(",")]
|
|
agent = pool.register_agent(
|
|
args.node, args.name, caps, args.model,
|
|
args.endpoint, args.max_concurrent, args.priority,
|
|
)
|
|
print(f"{GREEN}Registered:{RESET} {agent.road_id}")
|
|
print(f" Node: {agent.node} | Caps: {', '.join(agent.caps)} | Model: {agent.model or '-'}")
|
|
|
|
elif args.command in ("deregister", "dereg", "rm"):
|
|
if pool.deregister_agent(args.id):
|
|
print(f"{GREEN}Removed:{RESET} {args.id}")
|
|
else:
|
|
print(f"{RED}Not found:{RESET} {args.id}")
|
|
|
|
elif args.command in ("pickup", "pu", "ride"):
|
|
caps = [c.strip() for c in args.caps.split(",")] if args.caps else None
|
|
match = pool.pickup(args.prompt, args.prefer, caps)
|
|
|
|
if match["status"] == "matched":
|
|
print(f"{GREEN}Matched!{RESET}")
|
|
print(f" Ride: {match['ride_id']}")
|
|
print(f" Agent: {match['agent_name']} ({match['agent_model'] or 'native'})")
|
|
print(f" Node: {match['agent_node']} → {match['agent_ip'] or 'mesh'}")
|
|
print(f" Caps: {', '.join(match['needed_caps'])} → {', '.join(match['agent_caps'])}")
|
|
print(f" Score: {match['score']} ({match['alternatives']} alternatives)")
|
|
print(f" Why: {match['reason']}")
|
|
if match.get('agent_endpoint'):
|
|
print(f" Route: {match['agent_endpoint']}")
|
|
else:
|
|
print(f"{RED}No match.{RESET}")
|
|
print(f" Ride: {match['ride_id']}")
|
|
print(f" Need: {', '.join(match['needed_caps'])}")
|
|
print(f" Reason: {match['reason']}")
|
|
print(f" Fleet: {match['available_agents']}/{match['total_agents']} available")
|
|
|
|
elif args.command in ("complete", "done"):
|
|
if pool.complete_ride(args.ride_id):
|
|
print(f"{GREEN}Ride completed:{RESET} {args.ride_id}")
|
|
else:
|
|
print(f"{RED}Ride not found:{RESET} {args.ride_id}")
|
|
|
|
elif args.command == "detect":
|
|
caps = pool.detect_caps(args.prompt)
|
|
print(f"{AMBER}Detected capabilities:{RESET}")
|
|
for cap in caps:
|
|
desc = CAPABILITIES.get(cap, "Unknown")
|
|
print(f" {GREEN}{cap:<10}{RESET} — {desc}")
|
|
|
|
elif args.command in ("fleet", "ls", "list"):
|
|
print(pool.fleet_status())
|
|
|
|
elif args.command in ("riders", "rides"):
|
|
print(pool.riders_status())
|
|
|
|
elif args.command in ("explain", "x"):
|
|
print(pool.explain_ride(args.ride_id))
|
|
|
|
elif args.command in ("status", "st"):
|
|
total = len(pool.agents)
|
|
avail = sum(1 for a in pool.agents.values() if a.available)
|
|
active = sum(a.active_rides for a in pool.agents.values())
|
|
rides = len(pool.rides)
|
|
nodes = len(set(a.node for a in pool.agents.values()))
|
|
|
|
print(f"{PINK}CarPool Status{RESET}")
|
|
print(f" Agents: {avail}/{total} available across {nodes} nodes")
|
|
print(f" Rides: {active} active, {rides} total")
|
|
print(f" Caps: {len(CAPABILITIES)} capability types")
|
|
print(f" Nodes: {', '.join(sorted(set(a.node for a in pool.agents.values())))}")
|
|
|
|
elif args.command == "reset":
|
|
REGISTRY_FILE.unlink(missing_ok=True)
|
|
RIDES_FILE.unlink(missing_ok=True)
|
|
print(f"{AMBER}Registry cleared.{RESET}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|