#!/usr/bin/env python3 """ FLEET-010: Cross-Agent Task Delegation Protocol Phase 3: Orchestration. Agents create issues, assign to other agents, review PRs. Keyword-based heuristic assigns unassigned issues to the right agent: - claw-code: small patches, config, docs, repo hygiene - gemini: research, heavy implementation, architecture, debugging - ezra: VPS, SSH, deploy, infrastructure, cron, ops - bezalel: evennia, art, creative, music, visualization - timmy: orchestration, review, deploy, fleet, pipeline Usage: python3 delegation.py run # Full cycle: scan, assign, report python3 delegation.py status # Show current delegation state python3 delegation.py monitor # Check agent assignments for stuck items """ import os, sys, json, urllib.request from datetime import datetime, timezone from pathlib import Path GITEA_BASE = "https://forge.alexanderwhitestone.com/api/v1" TOKEN = Path(os.path.expanduser("~/.config/gitea/token")).read_text().strip() DATA_DIR = Path(os.path.expanduser("~/.local/timmy/fleet-resources")) LOG_FILE = DATA_DIR / "delegation.log" HEADERS = {"Authorization": f"token {TOKEN}"} AGENTS = { "claw-code": {"caps": ["patch","config","gitignore","cleanup","format","readme","typo"], "active": True}, "gemini": {"caps": ["research","investigate","benchmark","survey","evaluate","architecture","implementation"], "active": True}, "ezra": {"caps": ["vps","ssh","deploy","cron","resurrect","provision","infra","server"], "active": True}, "bezalel": {"caps": ["evennia","art","creative","music","visual","design","animation"], "active": True}, "timmy": {"caps": ["orchestrate","review","pipeline","fleet","monitor","health","deploy","ci"], "active": True}, } MONITORED = [ "Timmy_Foundation/timmy-home", "Timmy_Foundation/timmy-config", "Timmy_Foundation/the-nexus", "Timmy_Foundation/hermes-agent", ] def api(path, method="GET", data=None): url = f"{GITEA_BASE}{path}" body = json.dumps(data).encode() if data else None hdrs = dict(HEADERS) if data: hdrs["Content-Type"] = "application/json" req = urllib.request.Request(url, data=body, headers=hdrs, method=method) try: resp = urllib.request.urlopen(req, timeout=15) raw = resp.read().decode() return json.loads(raw) if raw.strip() else {} except urllib.error.HTTPError as e: body = e.read().decode() print(f" API {e.code}: {body[:150]}") return None except Exception as e: print(f" API error: {e}") return None def log(msg): ts = datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M:%S") DATA_DIR.mkdir(parents=True, exist_ok=True) with open(LOG_FILE, "a") as f: f.write(f"[{ts}] {msg}\n") def suggest_agent(title, body): text = (title + " " + body).lower() for agent, info in AGENTS.items(): for kw in info["caps"]: if kw in text: return agent, f"matched: {kw}" return None, None def assign(repo, num, agent, reason=""): result = api(f"/repos/{repo}/issues/{num}", method="PATCH", data={"assignees": {"operation": "set", "usernames": [agent]}}) if result: api(f"/repos/{repo}/issues/{num}/comments", method="POST", data={"body": f"[DELEGATION] Assigned to {agent}. {reason}"}) log(f"Assigned {repo}#{num} to {agent}: {reason}") return result def run_cycle(): log("--- Delegation cycle start ---") count = 0 for repo in MONITORED: issues = api(f"/repos/{repo}/issues?state=open&limit=50") if not issues: continue for i in issues: if i.get("assignees"): continue title = i.get("title", "") body = i.get("body", "") if any(w in title.lower() for w in ["epic", "discussion"]): continue agent, reason = suggest_agent(title, body) if agent and AGENTS.get(agent, {}).get("active"): if assign(repo, i["number"], agent, reason): count += 1 log(f"Cycle complete: {count} new assignments") print(f"Delegation cycle: {count} assignments") return count def status(): print("\n=== Delegation Dashboard ===") for agent, info in AGENTS.items(): count = 0 for repo in MONITORED: issues = api(f"/repos/{repo}/issues?state=open&limit=50") if issues: for i in issues: for a in (i.get("assignees") or []): if a.get("login") == agent: count += 1 icon = "ON" if info["active"] else "OFF" print(f" {agent:12s}: {count:>3} issues [{icon}]") if __name__ == "__main__": cmd = sys.argv[1] if len(sys.argv) > 1 else "run" DATA_DIR.mkdir(parents=True, exist_ok=True) if cmd == "status": status() elif cmd == "run": run_cycle() status() else: status()