#!/usr/bin/env python3 """ AI Agency State Management Utility ==================================== This script is a HELPER TOOL only — it does NOT run the AI. The AI orchestration logic lives in .ai_agency/AGENCY.md Use these utilities to: - View current project state - Manually reset state - Fix stuck/blocked states - Inspect backlog progress Usage: python orchestrate.py status # Show current state summary python orchestrate.py reset # Reset state to IDLE (start over) python orchestrate.py unblock # Clear BLOCKED_NEEDS_HUMAN status python orchestrate.py next-task # Show next pending task python orchestrate.py progress # Show backlog completion progress """ import os import sys import json from datetime import datetime AGENCY_DIR = ".ai_agency" STATE_FILE = os.path.join(AGENCY_DIR, "memory/state.json") BACKLOG_FILE = os.path.join(AGENCY_DIR, "memory/backlog.json") def load_json(path): if not os.path.exists(path): return None with open(path, "r", encoding="utf-8") as f: return json.load(f) def save_json(path, data): with open(path, "w", encoding="utf-8") as f: json.dump(data, f, indent=2, ensure_ascii=False) def cmd_status(): state = load_json(STATE_FILE) if not state: print("āŒ No state.json found. Agency has not been initialized yet.") return print(f"\n{'='*50}") print(f"šŸ¢ AI Agency Status") print(f"{'='*50}") print(f"Project : {state.get('project_name', 'Unknown')}") print(f"Mode : {state.get('project_mode', 'Unknown')}") print(f"Status : {state.get('status', 'Unknown')}") cp = state.get("checkpoint", {}) print(f"\nšŸ“ Checkpoint:") print(f" Active Agent : {cp.get('active_agent', 'None')}") print(f" Active Ticket : {cp.get('current_ticket_id', 'None')}") sub = cp.get("sub_step", {}) if sub: print(f" Sub-step : {sub.get('index')}/{sub.get('total')} — {sub.get('description', '')}") rc = state.get("resume_context", {}) if rc.get("last_completed_action"): print(f"\nšŸ’¾ Resume Context:") print(f" Last done : {rc.get('last_completed_action', '')}") print(f" Next todo : {rc.get('next_action', '')}") guards = state.get("execution_guards", {}) if guards.get("blocking_reason"): print(f"\n🚫 BLOCKED: {guards['blocking_reason']}") print(f"\nšŸ• Last Updated: {state.get('last_updated', 'Unknown')}") def cmd_progress(): backlog = load_json(BACKLOG_FILE) if not backlog: print("āŒ No backlog.json found.") return tasks = backlog.get("tasks", []) if not tasks: print("āŒ No tasks in backlog.") return total = len(tasks) completed = sum(1 for t in tasks if t.get("status") == "completed") pending = sum(1 for t in tasks if t.get("status") == "pending") blocked = sum(1 for t in tasks if t.get("status") in ["BLOCKED", "SECURITY_FAILED"]) print(f"\n{'='*50}") print(f"šŸ“‹ Backlog Progress") print(f"{'='*50}") print(f"Total : {total}") print(f"āœ… Done : {completed}") print(f"ā³ Pending: {pending}") print(f"āŒ Blocked: {blocked}") print(f"\nProgress : [{'ā–ˆ' * completed}{'ā–‘' * pending}] {int(completed/total*100)}%") print(f"\nšŸ“ Task List:") for task in tasks: icon = {"completed": "āœ…", "pending": "ā³", "COMPLETED_PENDING_QA": "šŸ”"}.get(task.get("status", ""), "ā“") print(f" {icon} [{task['priority']}] {task['id']}: {task['title'][:60]}") def cmd_next_task(): backlog = load_json(BACKLOG_FILE) if not backlog: print("āŒ No backlog.json found.") return tasks = backlog.get("tasks", []) completed_ids = [t["id"] for t in tasks if t.get("status") == "completed"] for task in tasks: if task.get("status") == "pending": deps = task.get("dependency_task_ids", []) if all(dep in completed_ids for dep in deps): print(f"\nšŸŽÆ Next Task Ready:") print(f" ID : {task['id']}") print(f" Title : {task['title']}") print(f" Role : {task['assigned_role']}") print(f" Priority : {task['priority']}") print(f" Est. : {task['estimated_minutes']} minutes") return print("āœ… No pending tasks with satisfied dependencies found.") def cmd_unblock(): state = load_json(STATE_FILE) if not state: print("āŒ No state.json found.") return state["status"] = "IN_PROGRESS" state["execution_guards"]["retry_count"] = 0 state["execution_guards"]["blocking_reason"] = None state["last_updated"] = datetime.now().isoformat() save_json(STATE_FILE, state) print(f"āœ… Status reset to IN_PROGRESS. Blocking reason cleared.") print(f" Active agent: {state['checkpoint'].get('active_agent')}") print(f" Resume the AI and say: 'continue agency work'") def cmd_reset(): confirm = input("āš ļø This will reset state to IDLE. All checkpoint progress will be lost. Type 'yes' to confirm: ") if confirm.strip().lower() != "yes": print("Aborted.") return state = { "schema_version": "2.0", "project_name": "UNINITIALIZED", "project_root": ".", "project_mode": "UNKNOWN", "status": "IDLE", "checkpoint": { "active_agent": "00_intake", "current_ticket_id": None, "sub_step": { "index": 0, "total": 0, "name": "", "description": "" } }, "resume_context": { "last_completed_action": None, "next_action": "Start from 00_intake — gather requirements", "files_modified_this_session": [], "files_pending": [], "notes": "Fresh start. Tell the AI: 'use the agency' to begin." }, "tech_stack": {}, "execution_guards": { "retry_count": 0, "max_retry_attempts": 3, "blocking_reason": None, "agent_visit_counts": {} }, "agent_call_log": [], "last_updated": datetime.now().isoformat() } save_json(STATE_FILE, state) print("āœ… State reset to IDLE. Start fresh by telling the AI: 'use the agency'") def main(): commands = { "status": cmd_status, "progress": cmd_progress, "next-task": cmd_next_task, "unblock": cmd_unblock, "reset": cmd_reset, } if len(sys.argv) < 2 or sys.argv[1] not in commands: print(__doc__) print("Available commands:", ", ".join(commands.keys())) sys.exit(1) commands[sys.argv[1]]() if __name__ == "__main__": main()