Files
blackroad/roadnet/carpool.py
Alexa Amundson 78fbe80f2a Initial monorepo — everything BlackRoad in one place
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
2026-03-14 17:08:41 -05:00

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()