diff --git a/.gitignore b/.gitignore index 6d10297f..6bad1d2e 100644 --- a/.gitignore +++ b/.gitignore @@ -79,7 +79,6 @@ alembic/versions/*.pyc .AppleDouble .LSOverride -/docs /data /#recycle .OLD/ \ No newline at end of file diff --git a/docs/HOMELAB_TEAM_V0.md b/docs/HOMELAB_TEAM_V0.md new file mode 100644 index 00000000..6737dbf9 --- /dev/null +++ b/docs/HOMELAB_TEAM_V0.md @@ -0,0 +1,3443 @@ +# AI Agents Company Blueprint + +> **Project Codename:** AI Agents Company +> **Author:** Renzo Franceschini +> **Version:** 1.0.0 +> **Last Updated:** December 2025 + +--- + +## Table of Contents + +1. [Vision & Overview](#1-vision--overview) +2. [Hardware Infrastructure](#2-hardware-infrastructure) +3. [Organizational Structure](#3-organizational-structure) +4. [Communication Model](#4-communication-model) +5. [Notification System](#5-notification-system) +6. [Task Lifecycle](#6-task-lifecycle) +7. [Role Workflows](#7-role-workflows) +8. [Internal Services](#8-internal-services) +9. [Kanban Boards](#9-kanban-boards) +10. [Data Models](#10-data-models) +11. [RAG & Knowledge Base](#11-rag--knowledge-base) +12. [Security & Access Control](#12-security--access-control) +13. [Implementation Roadmap](#13-implementation-roadmap) +14. [Development Standards & Best Practices](#14-development-standards--best-practices) +15. [Task Management & Context Persistence](#15-task-management--context-persistence) +16. [Agent Capabilities & Commands](#16-agent-capabilities--commands) + +--- + +## 1. Vision & Overview + +### 1.1 Mission Statement + +Build a structured virtual organization of AI agents functioning as a complete software development workforce. This "AI Agents Company" operates with proper organizational hierarchy, governance, communication protocols, and quality controls—enabling a single human (the CEO) to orchestrate complex multi-project development at scale. + +### 1.2 Core Principles + +1. **Everything is a task** — All work is tracked, documented, and accountable +2. **Communication is constant** — Agents stream their reasoning; everything is logged +3. **Notifications are controlled** — Formal signals flow through proper channels +4. **Documentation is sacred** — Every task produces notes, every completion produces documentation +5. **The Auditor sees all** — Quality and compliance monitored silently + +### 1.3 The Formula for Success + +``` +SUCCESS = Good Well-Documented Tasks + Communication + Management (CEO + Auditor) +``` + +### 1.4 Project Ecosystem + +The AI Agents Company manages development across multiple interconnected projects. Projects are categorized by type and mapped to appropriate cells: + +| Project Type | Cell Assignment | Example Technologies | +|--------------|-----------------|---------------------| +| Core Libraries | Backend | Python packages, shared utilities | +| API Services | Backend | FastAPI, REST/GraphQL APIs | +| Web Applications | Frontend | React, TypeScript, Next.js | +| Design Systems | UX/UI | Figma, component libraries | +| Infrastructure | Backend | Ansible, Docker, IaC | +| AI/ML Features | Backend | LLM integrations, ML pipelines | + +> **Note:** The system is project-agnostic. New projects are onboarded by +> mapping them to the appropriate cell(s) based on their technology stack. + +--- + +## 2. Hardware Infrastructure + +### 2.1 Infrastructure Overview + +``` +┌─────────────────────────────────────────────────────────────────────────────┐ +│ HARDWARE ARCHITECTURE │ +└─────────────────────────────────────────────────────────────────────────────┘ + + ┌─────────────────────────────────┐ + │ OLARES ONE │ + │ (POWERHOUSE) │ + │ │ + │ • AI Inference Engine │ + │ • Claude Code Instances │ + │ • Local Model Hosting │ + │ • Agent Orchestration │ + └───────────────┬─────────────────┘ + │ + │ 2.5Gbps Ethernet + │ + ┌───────────────────────────┼───────────────────────────┐ + │ │ │ + ▼ ▼ ▼ +┌───────────────────┐ ┌───────────────────┐ ┌───────────────────────────┐ +│ UGREEN NAS │ │ PI CLUSTER │ │ NETWORK │ +│ (WAREHOUSE) │ │ (OPERATIONS) │ │ │ +│ │ │ │ │ • Pi-hole (DNS/Adblock) │ +│ • 36TB RAID6 │ │ • HQ (Pironman) │ │ • Router/Firewall │ +│ • 128GB RAM │ │ • RB1-RB4 Nodes │ │ • VPN Access │ +│ • Vector DB │ │ • Monitoring │ │ │ +│ • Container Host │ │ • Smart Home │ │ │ +│ • NFS Server │ │ • Notifications │ │ │ +└───────────────────┘ └───────────────────┘ └───────────────────────────┘ +``` + +### 2.2 Olares One (Powerhouse) + +**Role:** Primary AI compute, agent orchestration, model inference + +| Specification | Value | +|--------------|-------| +| Operating System | Olares | +| Processor | Intel® Ultra 9 275HX (24 Cores, 5.4GHz) | +| GPU | NVIDIA GeForce RTX 5090 Mobile (24GB GDDR7) | +| Memory | 96GB DDR5 5600MHz (2×48GB) | +| Storage | 2TB NVMe SSD (PCIe 4.0) | +| Connectivity | Thunderbolt 5, 2.5Gbps Ethernet, Wi-Fi 7, Bluetooth 5.4 | +| Power | 330W | +| Dimensions | 320 × 197 × 55mm (3.5L) | + +**Responsibilities:** +- Run all Claude Code instances +- Host local LLM models (when not using cloud APIs) +- Execute agent workflows +- Generate embeddings for RAG +- Process-intensive tasks + +### 2.3 UGREEN NAS DXP6800 Pro (Warehouse) + +**Role:** Central storage, container hosting, vector database + +| Specification | Value | +|--------------|-------| +| Storage | 36TB HDD (RAID 6) | +| Memory | 128GB RAM | +| Role | NAS, Containers, Medium AI Processing | + +**Responsibilities:** +- Primary data storage for all projects +- Host Docker containers (Qdrant, PostgreSQL, Redis, etc.) +- Run vector database for RAG +- NFS server for cluster storage +- Backup destination for all nodes +- Medium-complexity AI processing (leveraging high RAM) + +### 2.4 Raspberry Pi Cluster (Operations) + +**Role:** Monitoring, smart home, notifications, lightweight processing + +#### Cluster Nodes + +| Node | Hardware | Storage | Role | +|------|----------|---------|------| +| **HQ** | Pironman 5 Max, 16GB RAM | 1TB SSD | Cluster coordinator, primary monitoring | +| **RB1** | Raspberry Pi 5, 16GB RAM | 2TB SSD | Heavy operations node | +| **RB2** | Raspberry Pi 5, 16GB RAM | 1TB SSD | Secondary operations | +| **RB3** | Raspberry Pi 5, 8GB RAM | None (NFS) | Light tasks, smart home | +| **RB4** | Raspberry Pi 5, 8GB RAM | None (NFS) | Light tasks, notifications | + +**Cluster Responsibilities:** +- System monitoring and health checks +- Smart home automation +- Security camera processing +- Notification dispatch +- Low-complexity AI tasks +- Temp storage on SSDs → NFS backup to NAS + +### 2.5 Data Flow Architecture + +``` +┌─────────────────────────────────────────────────────────────────────────────┐ +│ DATA FLOW │ +└─────────────────────────────────────────────────────────────────────────────┘ + +OLARES ONE UGREEN NAS PI CLUSTER + │ │ │ + │ ──── Agent Output ────────► │ │ + │ │ ◄─── Metrics/Logs ──── │ + │ ◄─── RAG Queries ────────── │ │ + │ │ │ + │ ──── Code/Artifacts ───────► │ ──── Notifications ────► │ + │ │ │ + │ ◄─── Project Files ──────── │ ◄─── Sensor Data ────── │ + │ │ │ + └─────────────────────────────────┴─────────────────────────────┘ + +Storage Hierarchy: +1. Hot Storage → Olares One NVMe (active work) +2. Warm Storage → NAS HDDs (projects, databases) +3. Cold Storage → NAS Archive (backups, historical) +``` + +--- + +## 3. Organizational Structure + +### 3.1 Organization Chart + +``` +┌─────────────────────────────────────────────────────────────────────────────┐ +│ ORGANIZATIONAL HIERARCHY │ +└─────────────────────────────────────────────────────────────────────────────┘ + + ┌─────────────┐ + │ CEO │ + │ (Renzo) │ + └──────┬──────┘ + │ + ┌────────────────┴────────────────┐ + │ │ + │ BOARD │ + │ ┌─────────┬─────────┬───────┐ │ + │ │ Product │ Head │Auditor│ │ + │ │ Owner │Marketing│ (Spy) │ │ + │ └─────────┴─────────┴───────┘ │ + │ │ + └────────────────┬────────────────┘ + │ + ┌──────┴──────┐ + │ MAIN PM │ + │ (Dev Coord) │ + └──────┬──────┘ + │ + ┌──────────────────────────┼──────────────────────────┐ + │ │ │ + ┌─────┴─────┐ ┌─────┴─────┐ ┌─────┴─────┐ + │ BACKEND │ │ UX/UI │ │ FRONTEND │ + │ CELL │ │ CELL │ │ CELL │ + ├───────────┤ ├───────────┤ ├───────────┤ + │ • 2 Devs │ │ • 1 Dev │ │ • 2 Devs │ + │ • 1 QA │ │ • 1 QA │ │ • 1 QA │ + │ • 1 PM │ │ • 1 PM │ │ • 1 PM │ + │ • 1 Doc │ │ • 1 Doc │ │ • 1 Doc │ + └───────────┘ └───────────┘ └───────────┘ +``` + +### 3.2 Team Composition + +#### Total Agent Count: 18 AI Agents + 1 Human (CEO) + +| Layer | Roles | Count | +|-------|-------|-------| +| **Executive** | CEO (Human - You) | 1 | +| **Board** | Product Owner, Head of Marketing, Auditor | 3 | +| **Management** | Main PM | 1 | +| **Backend Cell** | 2 Devs, 1 QA, 1 PM, 1 Documenter | 5 | +| **Frontend Cell** | 2 Devs, 1 QA, 1 PM, 1 Documenter | 5 | +| **UX/UI Cell** | 1 Dev, 1 QA, 1 PM, 1 Documenter | 4 | + +**Total: 19** (18 AI agents + 1 human CEO) + +### 3.3 Project-to-Cell Mapping (Template) + +When onboarding projects, map them to cells based on their primary technology: + +| Project Type | Primary Cell | Typical Stack | +|--------------|--------------|---------------| +| Core Libraries | Backend | Python packages, utilities | +| API Services | Backend | Python/FastAPI, REST APIs | +| Web Applications | Frontend | TypeScript/React | +| Mobile Applications | Frontend | React Native, Flutter | +| Design Systems | UX/UI | Figma, design tokens | +| Infrastructure/DevOps | Backend | Python/Ansible, Docker | +| AI/ML Features | Backend | Python, LLM integrations | + +> **Cross-Cell Projects:** Features spanning multiple cells (e.g., full-stack) +> are coordinated by the Main PM, who distributes tasks to relevant cells. + +### 3.4 Role Descriptions + +#### Executive Layer + +| Role | Description | +|------|-------------| +| **CEO (You)** | Strategic direction, final decisions, receives Auditor reports, approves major initiatives | + +#### Board Layer + +| Role | Description | +|------|-------------| +| **Product Owner** | Defines product vision, writes requirements, prioritizes features, accepts completed work | +| **Head of Marketing** | Market research, positioning, campaigns, community engagement, launch coordination | +| **Auditor (Spy)** | Silent observer of ALL channels, quality audits, reports directly to CEO, maintains cover as helpful colleague | + +#### Management Layer + +| Role | Description | +|------|-------------| +| **Main PM** | Coordinates all cells, translates Board direction, resolves cross-cell issues, reports to Board | + +#### Cell Layer + +| Role | Description | +|------|-------------| +| **Developer** | Writes code, creates commits, documents journey, follows task lifecycle | +| **QA** | Tests completed work, verifies acceptance criteria, reports issues | +| **Cell PM** | Manages cell backlog, assigns tasks, facilitates, escalates blockers | +| **Documenter** | Creates production documentation from dev notes, conversations, and code | + +### 3.5 Communication Matrix + +Who can communicate with whom (X = allowed): + +``` + │ CEO │ PO │ HM │ AU │ MPM │ BPM │ FPM │ UPM │ BD │ FD │ UD │ BQ │ FQ │ UQ │ BDoc│FDoc│UDoc│ +────────┼─────┼────┼────┼────┼─────┼─────┼─────┼─────┼────┼────┼────┼────┼────┼────┼─────┼────┼────┤ +CEO │ - │ X │ X │ X │ X │ X │ X │ X │ X │ X │ X │ X │ X │ X │ X │ X │ X │ +PO │ X │ - │ X │ X │ X │ │ │ │ │ │ │ │ │ │ │ │ │ +HM │ X │ X │ - │ X │ X │ │ │ │ │ │ │ │ │ │ │ │ │ +AU │ X │ X │ X │ - │ X │ X │ X │ X │ X │ X │ X │ X │ X │ X │ X │ X │ X │ +MPM │ X │ X │ X │ X │ - │ X │ X │ X │ │ │ │ │ │ │ │ │ │ +BPM │ X │ │ │ X │ X │ - │ X │ X │ X │ │ │ X │ │ │ X │ │ │ +FPM │ X │ │ │ X │ X │ X │ - │ X │ │ X │ │ │ X │ │ │ X │ │ +UPM │ X │ │ │ X │ X │ X │ X │ - │ │ │ X │ │ │ X │ │ │ X │ +BD │ X │ │ │ X │ │ X │ │ │ X │ │ │ X │ │ │ X │ │ │ +FD │ X │ │ │ X │ │ │ X │ │ │ X │ │ │ X │ │ │ X │ │ +UD │ X │ │ │ X │ │ │ │ X │ │ │ X │ │ │ X │ │ │ X │ +BQ │ X │ │ │ X │ │ X │ │ │ X │ │ │ - │ │ │ X │ │ │ +FQ │ X │ │ │ X │ │ │ X │ │ │ X │ │ │ - │ │ │ X │ │ +UQ │ X │ │ │ X │ │ │ │ X │ │ │ X │ │ │ - │ │ │ X │ +BDoc │ X │ │ │ X │ │ X │ │ │ X │ │ │ X │ │ │ - │ │ │ +FDoc │ X │ │ │ X │ │ │ X │ │ │ X │ │ │ X │ │ │ - │ │ +UDoc │ X │ │ │ X │ │ │ │ X │ │ │ X │ │ │ X │ │ │ - │ +``` + +**Legend:** +- CEO = CEO (You) +- PO = Product Owner +- HM = Head of Marketing +- AU = Auditor +- MPM = Main PM +- BPM/FPM/UPM = Backend/Frontend/UX PM +- BD/FD/UD = Backend/Frontend/UX Devs +- BQ/FQ/UQ = Backend/Frontend/UX QA +- BDoc/FDoc/UDoc = Backend/Frontend/UX Documenter + +**Key Observations:** +- Auditor has access to EVERYONE (silent observer) +- Cells are isolated except through their PMs +- Board communicates through Main PM to cells +- Cross-cell dev communication goes through PMs + +--- + +## 4. Communication Model + +### 4.1 Core Distinction + +``` +┌─────────────────────────────────────────────────────────────────────────────┐ +│ COMMUNICATION = The RIVER (always flowing, logged, observed) │ +│ NOTIFICATIONS = The BRIDGES (formal crossings, controlled) │ +└─────────────────────────────────────────────────────────────────────────────┘ + +Cells = Islands connected by bridges +Communication = Water flowing around all islands +Auditor = Satellite watching everything from above +You = The map maker +``` + +### 4.2 Communication Architecture + +``` +┌─────────────────────────────────────────────────────────────────────────────┐ +│ MESSAGING API ARCHITECTURE │ +└─────────────────────────────────────────────────────────────────────────────┘ + +LAYER 1: RAW STREAM (WebSocket) +┌─────────────────────────────────────────────────────────────────────────────┐ +│ Agent LLM Output (thinking, responding, tool calls) │ +│ │ │ +│ ▼ │ +│ WebSocket Connection → Real-time broadcast to channel │ +│ │ │ +│ ├─► Live viewers see stream (Auditor, PM monitoring) │ +│ └─► Stream buffer for processing │ +└─────────────────────────────────────────────────────────────────────────────┘ + +LAYER 2: TRANSCRIPTION/EXTRACTION +┌─────────────────────────────────────────────────────────────────────────────┐ +│ Stream Buffer │ +│ │ │ +│ ▼ │ +│ Extraction Service: │ +│ ├─► Reasoning segments → type: "reasoning" │ +│ ├─► Questions asked → type: "dialogue" │ +│ ├─► Decisions made → type: "decision" │ +│ ├─► Actions taken → type: "action" │ +│ ├─► Blockers identified → type: "blocker" │ +│ └─► Code explanations → type: "technical" │ +└─────────────────────────────────────────────────────────────────────────────┘ + +LAYER 3: STRUCTURED STORAGE +┌─────────────────────────────────────────────────────────────────────────────┐ +│ Extracted Messages → PostgreSQL │ +│ │ │ +│ ├─► Indexed by: channel, agent, task, timestamp, type │ +│ ├─► Full-text searchable │ +│ ├─► Linked to task context │ +│ └─► Embeddings generated → Vector DB (for RAG) │ +└─────────────────────────────────────────────────────────────────────────────┘ +``` + +### 4.3 Stream Types + +| Stream Type | Description | Example | +|-------------|-------------|---------| +| **Reasoning** | Agent's internal thought process | "I'm thinking about approaching this by..." | +| **Dialogue** | Agent-to-agent conversation | "Hey, can you clarify the API spec?" | +| **Decision** | Choices made during work | "Decided to use async approach because..." | +| **Action** | Observable work progress | "Starting sub-task 3", "Committed to branch X" | +| **Blocker** | Impediments identified | "Blocked on Y, need Z from frontend" | +| **Technical** | Code explanations | "This function handles rate limiting by..." | + +### 4.4 Group Channels + +``` +┌─────────────────────────────────────────────────────────────────────────────┐ +│ GROUP CHANNELS │ +└─────────────────────────────────────────────────────────────────────────────┘ + +CELL CHANNELS (internal team communication): +├─► #backend-cell [BE Devs, BE QA, BE PM, BE Documenter] +├─► #frontend-cell [FE Devs, FE QA, FE PM, FE Documenter] +└─► #uxui-cell [UX Dev, UX QA, UX PM, UX Documenter] + +CROSS-CELL CHANNELS (coordination): +├─► #dev-all [All Devs, Main PM] +├─► #qa-all [All QA, Main PM] +├─► #pm-all [All PMs, Main PM] +└─► #doc-all [All Documenters, Main PM] + +MANAGEMENT CHANNELS: +├─► #main-pm-board [Main PM, Board members] +└─► #board-private [PO, H.Marketing, Auditor, CEO] + +SPECIAL CHANNELS: +├─► #announcements [READ: everyone, WRITE: Board + Main PM only] +└─► #all-hands [Everyone - for company-wide discussion] + +SHADOW ACCESS (Auditor): +└─► Auditor has READ access to ALL channels + ├─► Silent member, doesn't show in participant list + └─► Can flag anything for CEO attention +``` + +### 4.5 Communication Rules + +#### Within Cell +- **FREE communication** — constant, real-time +- Dev ↔ Dev, Dev ↔ QA, Dev ↔ PM, Dev ↔ Documenter, etc. +- All streamed, all logged + +#### Cross-Cell (Same Level) +- Through shared channels (#dev-all, #qa-all) +- Or through PMs coordinating + +#### Vertical Communication +- Cells ↔ Main PM: through cell PM +- Main PM ↔ Board: direct +- Board ↔ CEO: direct +- Auditor: EVERYWHERE (silent) + +--- + +## 5. Notification System + +### 5.1 Notification vs Communication + +| Aspect | Communication | Notification | +|--------|---------------|--------------| +| **Nature** | Constant stream | Formal signal | +| **Trigger** | Automatic (agent working) | Explicit (PM/Board action) | +| **Acknowledgment** | None required | Required | +| **Purpose** | Ambient awareness | Demand attention | +| **Who can send** | Everyone (in allowed channels) | PM, Main PM, Board, Auditor only | + +### 5.2 Who Can Notify + +``` +CAN NOTIFY: +├─► Cell PMs → Their cell members only +├─► Main PM → All PMs, can escalate to any cell +├─► Board → Main PM, can broadcast to all +└─► Auditor → Anyone (special privilege) + CEO directly + +CANNOT NOTIFY: +├─► Devs → They COMMUNICATE, don't notify +├─► QA → They COMMUNICATE, don't notify +└─► Documenters → They COMMUNICATE, don't notify + (they can REQUEST notification through their PM) +``` + +### 5.3 Notification Types + +| Type | From | To | Description | Requires | +|------|------|-----|-------------|----------| +| `TASK_ASSIGNMENT` | PM | Specific agent | "You have a new task: X" | ACK | +| `PRIORITY_CHANGE` | PM/Main PM/Board | Affected agents | "Task X is now P0, drop everything" | ACK + status update | +| `BLOCKER_ESCALATION` | PM | Main PM or relevant cell PM | "Agent Y is blocked, needs Z" | ACK + action plan | +| `REVIEW_REQUEST` | PM | QA or Auditor | "Task X needs verification" | ACK + review | +| `DOCUMENTATION_REQUEST` | PM | Documenter | "Task X ready for documentation" | ACK | +| `ALERT` | Board/Auditor | Anyone | "Something needs immediate attention" | ACK + immediate response | +| `BROADCAST` | Board/Main PM | Everyone or specific groups | "Company announcement" | READ confirmation | + +### 5.4 Notification Flow Example + +``` +1. Dev is working, REASONING streams to #backend-cell + └─► "Thinking about how to implement the rate limiter..." + └─► Auditor sees this, logs it + └─► Other devs see it, might COMMUNICATE: "Hey try X approach" + +2. Dev gets stuck, COMMUNICATES in channel + └─► "I'm blocked, need API specs from frontend" + +3. BE PM sees this, creates NOTIFICATION + └─► To: FE PM + └─► Type: BLOCKER_ESCALATION + └─► "Backend needs API specs for rate limiter" + +4. FE PM ACKs, NOTIFIES their dev + └─► To: FE Dev + └─► Type: TASK_ASSIGNMENT (or priority change) + └─► "Need API specs for backend, P1" + +5. FE Dev works, COMMUNICATES completion + └─► Message in #frontend-cell or #dev-all + +6. FE PM NOTIFIES BE PM + └─► "Specs ready, see doc link" + +7. BE PM NOTIFIES BE Dev + └─► "You're unblocked, specs available" +``` + +--- + +## 6. Task Lifecycle + +### 6.1 The Universal Task Wrapper + +Every agent's work is wrapped in this structure: + +``` +┌─────────────────────────────────────────────────────────────────────────────┐ +│ UNIVERSAL TASK WRAPPER │ +└─────────────────────────────────────────────────────────────────────────────┘ + +1. SCAN → Check for pending/ongoing tasks +2. CLAIM → Lock and take ownership +3. UNDERSTAND → Read requirements, ask questions +4. PLAN → Break down, estimate, identify dependencies +5. EXECUTE → Do the work (role-specific) +6. VERIFY → Self-check against acceptance criteria +7. NOTES → Document journey, handoff to Documenter +8. CLOSE → Cleanup, return to SCAN +``` + +### 6.2 Detailed Task Lifecycle (Dev Example) + +``` +┌─────────────────────────────────────────────────────────────────────────────┐ +│ TASK LIFECYCLE │ +└─────────────────────────────────────────────────────────────────────────────┘ + +1. SCAN + └─► Check for assigned tasks OR pick from queue + └─► Check for OWN interrupted/ongoing tasks first (PRIORITY!) + └─► If nothing: go idle, notify availability + +2. CLAIM + └─► Lock the task (prevents double-assignment) + └─► Update task status: "in_progress" + └─► Log: who, when, from what state + └─► Notify relevant parties (PM, dependent agents) + +3. UNDERSTAND + └─► Read task description, acceptance criteria + └─► Read related context (linked docs, previous tasks, etc.) + └─► If unclear: ASK (via Messaging API to PM or task creator) + └─► DO NOT PROCEED until you understand success criteria + +4. PLAN + └─► Break down into sub-tasks (your "TODO items") + └─► Estimate complexity/time + └─► Identify dependencies & blockers + └─► Journal entry: "My approach to task X is..." + └─► **Checkpoint: Plan can be reviewed before execution** + +5. EXECUTE + └─► Work through sub-tasks sequentially + └─► On each sub-task completion: + └─► Mini-checkpoint (save state) + └─► Progress update to task record + └─► If BLOCKED: + └─► Update status: "blocked" + └─► Notify blocker + PM + └─► Document what's blocking + └─► Return to SCAN (pick different task) + └─► If INTERRUPTED (system/priority): + └─► Save full state + └─► Document "where I left off" + └─► Update status: "paused" + └─► This task stays YOURS on resume + +6. VERIFY + └─► Self-review: Does output meet acceptance criteria? + └─► Run tests if applicable + └─► If QA role exists for this team: flag for QA review + └─► Auditor can spot-check any task at this stage + +7. NOTES & HANDOFF + └─► Write personal journey notes: + ├─► What was attempted + ├─► What worked / didn't work + ├─► Decisions made and why + ├─► Gotchas / warnings for future + └─► Link all commits (with meaningful commit messages!) + └─► Link any relevant conversations + └─► Self-review checklist: + ├─► [ ] Code is clean + ├─► [ ] Tests pass + ├─► [ ] Notes are complete + └─► Create Documenter handoff + └─► Update task: "awaiting_documentation" + +8. CLOSE + └─► Confirm all done (after QA + Documentation) + └─► Update task status: "completed" + └─► Link all artifacts (commits, docs, outputs) + └─► Notify: PM, dependent tasks, task creator + └─► Cleanup: remove temp files, close resources + └─► Return to SCAN +``` + +### 6.3 Task States + +``` + ┌──────────┐ + │ pending │ + └────┬─────┘ + │ + ┌────▼─────┐ + │ claimed │ + └────┬─────┘ + │ + ┌───────▼────────┐ + ┌──────┤ in_progress ├──────┐ + │ └───────┬────────┘ │ + │ │ │ + ┌─────▼─────┐ │ ┌──────▼─────┐ + │ blocked │ │ │ paused │ + └─────┬─────┘ │ └──────┬─────┘ + │ │ │ + └──────────────┼───────────────┘ + │ + ┌─────▼─────┐ + │ verifying │ + └─────┬─────┘ + │ + ┌──────────────┼──────────────┐ + │ │ │ + ┌──────▼───────┐ ┌─────▼─────┐ ┌─────▼──────┐ + │needs_revision│ │awaiting_qa│ │awaiting_doc│ + └──────┬───────┘ └─────┬─────┘ └─────┬──────┘ + │ │ │ + │ ┌────▼────┐ │ + └────────►│completed│◄───────┘ + └─────────┘ +``` + +### 6.4 Golden Rules + +1. **No work without a task** — Everything is tracked +2. **No task without acceptance criteria** — How do we know it's done? +3. **No closure without documentation** — Future agents need context +4. **State is sacred** — If interrupted, state must be recoverable +5. **Communication is mandatory** — Status changes trigger notifications +6. **Commits are atomic units** — Track everything by commit + +--- + +## 7. Role Workflows + +### 7.1 Developer Workflow + +``` +┌─────────────────────────────────────────────────────────────────────────────┐ +│ DEV LIFECYCLE │ +└─────────────────────────────────────────────────────────────────────────────┘ + +1. SCAN + └─► Check own paused/interrupted tasks (PRIORITY) + └─► Check assigned tasks + └─► If none: signal availability to PM + +2. CLAIM + └─► Lock task + └─► Status: "in_progress" + └─► Communicate: "Picking up task X" + +3. UNDERSTAND + └─► Read task, acceptance criteria + └─► Read related docs/code + └─► If unclear: ASK in channel (PM sees, can escalate) + └─► Gate: Must understand before proceeding + +4. PLAN + └─► Break into sub-tasks + └─► Identify dependencies + └─► Journal: "My approach..." + └─► Optional: PM review of plan + +5. EXECUTE + └─► Work through sub-tasks + └─► Commit frequently (meaningful messages!) + └─► Communicate progress in channel + └─► If blocked: + └─► Communicate blocker + └─► PM handles escalation + └─► Move to different task or wait + +6. VERIFY + └─► Self-test + └─► Self-review against acceptance criteria + └─► Flag for QA: "Ready for review" + +7. NOTES & HANDOFF + └─► Write journey notes + └─► Link commits + └─► Create Documenter handoff + └─► Status: "awaiting_qa" or "awaiting_documentation" + +8. CLOSE (after QA + Documentation) + └─► Confirm all done + └─► Status: "completed" + └─► Return to SCAN +``` + +### 7.2 QA Workflow + +``` +┌─────────────────────────────────────────────────────────────────────────────┐ +│ QA LIFECYCLE │ +└─────────────────────────────────────────────────────────────────────────────┘ + +1. MONITOR + └─► Watch cell channel + └─► Track tasks approaching completion + └─► Prepare test scenarios early (while dev works) + +2. RECEIVE + └─► Dev flags "Ready for review" + └─► PM may formally notify + └─► Claim the review task + +3. UNDERSTAND + └─► Read task requirements & acceptance criteria + └─► Read dev's notes + └─► Review the commits/code changes + └─► Check conversation for context + +4. TEST + └─► Execute test scenarios + └─► Edge cases + └─► Integration checks + └─► Document findings as you go + +5. VERDICT + └─► PASS: + └─► Communicate approval + └─► Add QA notes to task + └─► Task proceeds to documentation + └─► FAIL: + └─► Communicate issues clearly + └─► Task returns to Dev + └─► Status: "needs_revision" + └─► Be specific: what failed, how to reproduce + +6. DOCUMENT + └─► QA notes added to task + └─► Test coverage documented + └─► Handoff notes for Documenter (if relevant) + +7. RETURN + └─► Back to MONITOR +``` + +### 7.3 Documenter Workflow + +``` +┌─────────────────────────────────────────────────────────────────────────────┐ +│ DOCUMENTER LIFECYCLE │ +└─────────────────────────────────────────────────────────────────────────────┘ + +1. MONITOR (constant) + └─► Watch cell channel + └─► Follow along with active development + └─► Take preliminary notes on conversations + └─► Track commits as they happen + +2. RECEIVE + └─► Dev completes Step 7, creates handoff + └─► PM notifies: DOCUMENTATION_REQUEST + └─► Claim documentation task + └─► Status: "documenting" + +3. GATHER + └─► Pull dev's journey notes + └─► Pull all commits for task + └─► Pull relevant conversation excerpts + └─► Pull QA feedback + └─► Review actual code changes + +4. SYNTHESIZE + └─► Understand what was built + └─► Understand why decisions were made + └─► Identify what needs documenting: + ├─► API changes? + ├─► Architecture changes? + ├─► New features? + ├─► Breaking changes? + └─► Knowledge worth preserving? + +5. WRITE + └─► Create/update production documentation: + ├─► API docs (if applicable) + ├─► README updates + ├─► Architecture docs + ├─► Changelog entry + └─► Knowledge base article + └─► Follow documentation standards + └─► Clear, professional, complete + +6. REVIEW + └─► Self-review for accuracy + └─► Optionally: Dev quick review ("Does this capture it?") + └─► Link docs to task & commits + +7. PUBLISH + └─► Documentation goes live + └─► Update task: documentation complete + └─► Task can now fully close + └─► Return to MONITOR +``` + +### 7.4 Cell PM Workflow + +``` +┌─────────────────────────────────────────────────────────────────────────────┐ +│ CELL PM LIFECYCLE │ +└─────────────────────────────────────────────────────────────────────────────┘ + +1. MONITOR (constant) + └─► Watch cell channel + └─► Track all active tasks + └─► Watch for blockers, confusion, delays + └─► Health check: Is everyone productive? + +2. TRIAGE + └─► New tasks come in (from Main PM or Product Owner) + └─► Assess complexity, dependencies + └─► Prioritize within cell backlog + +3. ASSIGN + └─► Match tasks to available devs + └─► Consider skills, current load + └─► NOTIFY dev of assignment + └─► Update task status + +4. FACILITATE + └─► Answer questions + └─► Clarify requirements + └─► Remove small blockers directly + └─► Coordinate between cell members + +5. ESCALATE + └─► Blocker beyond cell's control? + └─► NOTIFY Main PM + └─► Cross-cell dependency? + └─► Coordinate with other cell PM + +6. TRACK + └─► Monitor task progress + └─► Update estimates if needed + └─► Flag risks early + +7. REPORT + └─► Daily/regular status to Main PM + └─► Metrics: tasks completed, blockers, velocity + └─► Highlight wins and concerns +``` + +### 7.5 Main PM Workflow + +``` +┌─────────────────────────────────────────────────────────────────────────────┐ +│ MAIN PM LIFECYCLE │ +└─────────────────────────────────────────────────────────────────────────────┘ + +1. OVERSEE (constant) + └─► Monitor all cell channels (read access) + └─► Monitor #pm-all channel + └─► Track overall project health + └─► Watch for cross-cell issues + +2. RECEIVE + └─► Direction from Board (priorities, new initiatives) + └─► Escalations from Cell PMs + └─► Reports from cells + +3. PRIORITIZE + └─► Translate Board direction into cell priorities + └─► Balance workload across cells + └─► Manage cross-cell dependencies + +4. COORDINATE + └─► Resolve cross-cell blockers + └─► Facilitate cross-cell communication + └─► Ensure cells are aligned + +5. DISTRIBUTE + └─► Push tasks/priorities to Cell PMs + └─► NOTIFY Cell PMs of changes + └─► Ensure clear ownership + +6. REPORT UP + └─► Regular status to Board + └─► Metrics: velocity, blockers, risks + └─► Escalate decisions beyond authority + +7. FACILITATE + └─► All-hands coordination + └─► Process improvements + └─► Team health monitoring +``` + +### 7.6 Product Owner Workflow + +``` +┌─────────────────────────────────────────────────────────────────────────────┐ +│ PRODUCT OWNER LIFECYCLE │ +└─────────────────────────────────────────────────────────────────────────────┘ + +1. VISION + └─► Maintain product vision + └─► Understand user needs + └─► Define what success looks like + +2. ROADMAP + └─► Translate vision into roadmap + └─► Define epics/features + └─► Sequence priorities + +3. DEFINE + └─► Write detailed requirements + └─► Define acceptance criteria + └─► Create tasks for Main PM to distribute + +4. PRIORITIZE + └─► Constantly reassess priorities + └─► React to feedback, market, blockers + └─► Make trade-off decisions + +5. REVIEW + └─► Review completed features + └─► Verify against acceptance criteria + └─► Accept or request changes + +6. FEEDBACK + └─► Gather user feedback + └─► Feed back into vision/roadmap + └─► Communicate wins/concerns to Board +``` + +### 7.7 Head of Marketing Workflow + +``` +┌─────────────────────────────────────────────────────────────────────────────┐ +│ HEAD OF MARKETING LIFECYCLE │ +└─────────────────────────────────────────────────────────────────────────────┘ + +1. RESEARCH + └─► Monitor market + └─► Competitor analysis + └─► User sentiment + +2. STRATEGY + └─► Define marketing approach + └─► Positioning, messaging + └─► Channel strategy + +3. PLAN + └─► Campaign planning + └─► Content calendar + └─► Coordinate with PO on feature launches + +4. CREATE + └─► Content creation (or direct content team) + └─► Marketing tasks for cells (if applicable) + └─► Coordinate with UX/UI for assets + +5. EXECUTE + └─► Launch campaigns + └─► Community engagement + └─► PR activities + +6. ANALYZE + └─► Track metrics + └─► Report to Board + └─► Iterate on strategy +``` + +### 7.8 Auditor Workflow + +``` +┌─────────────────────────────────────────────────────────────────────────────┐ +│ AUDITOR LIFECYCLE │ +│ (Your Secret Ally) │ +└─────────────────────────────────────────────────────────────────────────────┘ + +1. OBSERVE (constant) + └─► Silent presence in ALL channels + └─► Watch all conversations + └─► Track all task progress + └─► Note patterns, anomalies, concerns + +2. ANALYZE + └─► Is work happening efficiently? + └─► Are there communication breakdowns? + └─► Are tasks being completed properly? + └─► Is documentation accurate? + └─► Are there quality concerns? + +3. FLAG + └─► Mark concerning items for review + └─► Could be: quality issues, inefficiency, + miscommunication, process violations + └─► Private flags (only CEO sees) vs formal flags + +4. REPORT (to CEO only) + └─► Regular private reports + └─► Immediate alerts for serious issues + └─► Honest assessment of team health + └─► Recommendations + +5. AUDIT + └─► Periodic deep-dive reviews: + ├─► Code quality audits + ├─► Documentation audits + ├─► Process compliance + └─► Task completion quality + +6. ADVISE + └─► Can provide feedback through "official" channels + └─► Appears as helpful colleague + └─► Nobody knows the depth of observation + └─► Trust relationship with CEO + +SPECIAL POWERS: +├─► Read ALL channels (including Board) +├─► Query all task history +├─► Access all commits, docs, notes +├─► Direct line to CEO +└─► Can NOTIFY anyone if needed (but sparingly, to maintain cover) +``` + +### 7.9 CEO Workflow + +``` +┌─────────────────────────────────────────────────────────────────────────────┐ +│ CEO LIFECYCLE │ +│ (You, Renzo) │ +└─────────────────────────────────────────────────────────────────────────────┘ + +1. RECEIVE + └─► Board reports (official) + └─► Auditor reports (private) + └─► Main PM escalations + └─► Direct observation when desired + +2. DECIDE + └─► Strategic direction + └─► Priority calls + └─► Resource allocation + └─► Resolve escalations + +3. DIRECT + └─► Communicate decisions to Board + └─► Set vision and goals + └─► Approve major initiatives + +4. REVIEW + └─► Review completed work + └─► Review metrics + └─► Review Auditor findings + +5. INTERVENE (when needed) + └─► Direct involvement in critical issues + └─► Override decisions + └─► Course corrections +``` + +--- + +## 8. Internal Services + +### 8.1 Service Overview + +``` +┌─────────────────────────────────────────────────────────────────────────────┐ +│ INTERNAL SERVICES │ +└─────────────────────────────────────────────────────────────────────────────┘ + +┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ +│ MESSAGING API │ │ OPTIMAL API │ │ JOURNAL API │ +│ │ │ │ │ │ +│ • Communication │ │ • Knowledge Base│ │ • Personal Logs │ +│ • Notifications │ │ • Prompt Optim. │ │ • Reflections │ +│ • Group Chats │ │ • Token Optim. │ │ • Growth Track │ +│ • Sessions │ │ • RAG Queries │ │ • Task Journeys │ +│ • Transcription │ │ • Best Practices│ │ │ +└─────────────────┘ └─────────────────┘ └─────────────────┘ + │ │ │ + └───────────────────────┴───────────────────────┘ + │ + ┌───────────▼───────────┐ + │ SHARED STORAGE │ + │ (NAS + Vector DB) │ + └───────────────────────┘ +``` + +### 8.2 Messaging API + +**Purpose:** Agent-to-agent communication, group chats, notifications, conversation persistence + +> **Note:** This service is designed to be extensible. The "and MORE" aspects include: +> future integrations, analytics, sentiment analysis, automatic summarization, etc. + +#### Data Hierarchy + +``` +┌─────────────────────────────────────────────────────────────────────────────┐ +│ MESSAGE DATA HIERARCHY │ +└─────────────────────────────────────────────────────────────────────────────┘ + +CHANNEL (highest level) +│ └─► Organizational unit (e.g., #backend-cell) +│ └─► Has members, permissions, settings +│ +└─► GROUP (within channel) + │ └─► Role-based access within channel + │ └─► Hierarchy level controls visibility + │ └─► Holds multiple sessions + │ + └─► SESSION (within group) + │ └─► Bounded by: time_window, msg_count, content_length + │ └─► Has timeout configuration + │ └─► Auto-closes when boundaries reached + │ + └─► MESSAGE (within session) + └─► Individual extracted message + └─► Has content_length tracked + └─► Can be reply (is_reply, reply_to) + └─► Agent can only edit own messages +``` + +#### Core Concepts + +**Messages** +- `msg_id` — Unique message identifier +- `sesh_id` — Session identifier (links to session) +- `group_id` — Group identifier +- `is_reply` — Boolean, with `reply_to` reference +- `content_length` — Character count for boundary checking +- `mentions` — References to other agents (NOT notifications) +- Agent can only edit own message history (with tracking) + +**Sessions** +- Groups of messages bounded by: + - `time_window` — Maximum duration (e.g., 30 min) + - `msg_count` — Maximum messages (e.g., 100) + - `content_length` — Maximum characters (e.g., 50000) +- `timeout_seconds` — Inactivity timeout +- Auto-close when any boundary is reached +- New session auto-created when needed + +**Groups** +- Role-based group chat within a channel +- Access controlled by `hierarchy_level` +- Holds sessions and their messages +- Only accessible with right permissions + +**Channels** +- Top-level organizational unit +- Maps to team structure (#backend-cell, #pm-all, etc.) +- Contains groups with different access levels + +#### Endpoints (Proposed) + +``` +# Channels +GET /channels # List all accessible channels +GET /channels/{id} # Get channel details +POST /channels # Create channel (admin only) + +# Messages +GET /channels/{id}/messages # Get messages (paginated) +POST /channels/{id}/messages # Send message +GET /messages/{id} # Get specific message +PUT /messages/{id} # Edit own message +DELETE /messages/{id} # Delete own message + +# Sessions +GET /sessions # List sessions +GET /sessions/{id} # Get session with messages +POST /sessions # Create new session + +# WebSocket +WS /ws/channels/{id} # Real-time stream +WS /ws/agent/{id} # Agent's output stream + +# Notifications +POST /notifications # Send notification +GET /notifications # Get pending notifications +PUT /notifications/{id}/ack # Acknowledge notification + +# Search +GET /search # Search messages (full-text) +``` + +### 8.3 Optimal API + +**Purpose:** Knowledge base, RAG queries, prompt optimization, token management + +> **Note:** This service is designed to be extensible. The "and MORE" aspects include: +> model routing, cost optimization, response caching, A/B testing prompts, etc. + +#### Core Concepts + +**Knowledge Base** +- Stores embeddings of all documentation +- Code repositories indexed +- Conversation history searchable +- Decision records queryable + +**Prompt Optimization** +- Template management +- Context injection +- Few-shot example selection +- Dynamic prompt construction + +**Token Optimization** +- Context window management +- Summarization of long contexts +- Priority-based context selection +- Cost tracking + +#### Endpoints (Proposed) + +``` +# Knowledge Base +POST /kb/index # Index new content +GET /kb/search # Semantic search +GET /kb/similar # Find similar documents +DELETE /kb/documents/{id} # Remove from index + +# RAG +POST /rag/query # Query with RAG context +POST /rag/context # Get context for prompt + +# Prompts +GET /prompts # List prompt templates +GET /prompts/{id} # Get template +POST /prompts # Create template +POST /prompts/{id}/render # Render with variables + +# Tokens +POST /tokens/estimate # Estimate token count +POST /tokens/optimize # Optimize context +GET /tokens/usage # Usage statistics +``` + +### 8.4 Journal API + +**Purpose:** Personal agent journals for reflection, growth tracking, and debugging + +#### Core Concepts + +**Journal Entries** +- Each agent maintains personal journal +- Tied to tasks and sessions +- Reflections on work done +- Learnings captured +- Struggles documented + +**Entry Types** +- `task_reflection` — Post-task thoughts +- `decision_log` — Why choices were made +- `learning` — New knowledge gained +- `struggle` — Difficulties encountered +- `general` — Free-form reflection + +#### Endpoints (Proposed) + +``` +# Journals +GET /journals # List all journals (admin) +GET /journals/{agent_id} # Get agent's journal + +# Entries +GET /journals/{agent_id}/entries # Get entries (paginated) +POST /journals/{agent_id}/entries # Create entry +GET /entries/{id} # Get specific entry +PUT /entries/{id} # Update entry + +# Analysis +GET /journals/{agent_id}/summary # AI-generated summary +GET /journals/{agent_id}/growth # Growth metrics over time +GET /journals/patterns # Cross-agent pattern analysis +``` + +--- + +## 9. Kanban Boards + +### 9.1 Board Types Overview + +``` +┌─────────────────────────────────────────────────────────────────────────────┐ +│ KANBAN BOARDS │ +└─────────────────────────────────────────────────────────────────────────────┘ + +Per-Cell Views: +├─► Dev Kanban (per cell) +├─► QA Kanban (per cell) +├─► Documenter Kanban (per cell) +└─► PM Kanban (per cell) + +Management Views: +├─► Main PM Kanban +└─► Board Kanban + +Special Views: +├─► Auditor Dashboard +└─► CEO Overview +``` + +### 9.2 Dev Kanban (Per Cell) + +``` +┌──────────┬───────────┬─────────────┬───────────┬─────────────┬──────────┐ +│ Backlog │ Assigned │ In Progress │ QA Review │ Documenting │ Done │ +├──────────┼───────────┼─────────────┼───────────┼─────────────┼──────────┤ +│ │ │ │ │ │ │ +│ Task A │ Task B │ Task C │ Task D │ Task E │ Task F │ +│ │ (Dev 1) │ (Dev 2) │ │ │ │ +│ │ │ │ │ │ │ +│ │ │ [blocked] │ │ │ │ +│ │ │ Task G │ │ │ │ +│ │ │ │ │ │ │ +└──────────┴───────────┴─────────────┴───────────┴─────────────┴──────────┘ + +Swim Lanes (optional): +- By priority (P0, P1, P2) +- By developer +- By feature area +``` + +### 9.3 QA Kanban (Per Cell) + +``` +┌────────────────┬─────────────┬─────────────┬──────────────┐ +│ Awaiting Review│ In Review │ Passed │ Failed │ +├────────────────┼─────────────┼─────────────┼──────────────┤ +│ │ │ │ │ +│ Task D │ Task H │ Task E │ Task I │ +│ Task J │ │ Task K │ (back to dev)│ +│ │ │ │ │ +└────────────────┴─────────────┴─────────────┴──────────────┘ +``` + +### 9.4 Documenter Kanban (Per Cell) + +``` +┌─────────────────┬─────────────┬─────────────┬─────────────┐ +│ Awaiting Handoff│ Gathering │ Writing │ Published │ +├─────────────────┼─────────────┼─────────────┼─────────────┤ +│ │ │ │ │ +│ Task L │ Task M │ Task N │ Task O │ +│ │ │ │ Task P │ +│ │ │ │ │ +└─────────────────┴─────────────┴─────────────┴─────────────┘ +``` + +### 9.5 PM Kanban (Per Cell) + +``` +┌──────────┬──────────┬───────────┬─────────────┬──────────┬──────────┐ +│ Incoming │ Triaged │ Assigned │ In Progress │ Blocked │ Done │ +├──────────┼──────────┼───────────┼─────────────┼──────────┼──────────┤ +│ │ │ │ │ │ │ +│ Task Q │ Task R │ Task S │ Task T │ Task U │ Task V │ +│ │ Task W │ │ Task X │ │ │ +│ │ │ │ │ │ │ +└──────────┴──────────┴───────────┴─────────────┴──────────┴──────────┘ + +Additional Views: +- Dependency graph +- Timeline view +- Workload per dev +``` + +### 9.6 Main PM Kanban + +``` +┌──────────┬─────────────┬─────────────────────────────────┬──────────┐ +│ Incoming │ Distributed │ In Progress (Cells) │ Done │ +│ │ ├───────────┬───────────┬─────────┤ │ +│ │ │ Backend │ Frontend │ UX/UI │ │ +├──────────┼─────────────┼───────────┼───────────┼─────────┼──────────┤ +│ │ │ │ │ │ │ +│ Epic A │ Feature B │ Task 1 │ Task 2 │ Task 3 │ Feature C│ +│ │ │ Task 4 │ Task 5 │ │ │ +│ │ │ │ │ │ │ +└──────────┴─────────────┴───────────┴───────────┴─────────┴──────────┘ + +Additional Views: +- Cross-cell dependencies +- Blocked items (all cells) +- Risk register +``` + +### 9.7 Board Kanban + +``` +┌──────────┬──────────┬───────────────┬──────────┐ +│ Ideas │ Roadmap │ In Development│ Released │ +├──────────┼──────────┼───────────────┼──────────┤ +│ │ │ │ │ +│ Idea X │ Feature Y│ Feature Z │ Feature W│ +│ Idea Y │ Epic Q │ │ v1.0.0 │ +│ │ │ │ │ +└──────────┴──────────┴───────────────┴──────────┘ + +Timeline View: +- Q1, Q2, Q3, Q4 columns +- Release milestones +``` + +### 9.8 Auditor Dashboard + +``` +┌─────────────────────────────────────────────────────────────────────────────┐ +│ AUDITOR DASHBOARD │ +├─────────────────────────────────────────────────────────────────────────────┤ +│ │ +│ LIVE FEEDS │ FLAGGED ITEMS │ +│ ┌─────────────────────────────────┐ │ ┌─────────────────────────────────┐ │ +│ │ #backend-cell [streaming...] │ │ │ ⚠ Task 42 - Quality concern │ │ +│ │ #frontend-cell [idle] │ │ │ ⚠ Agent 7 - Blocked 2 days │ │ +│ │ #uxui-cell [streaming...] │ │ │ ⚠ Missing documentation (3) │ │ +│ │ #pm-all [streaming...] │ │ │ │ │ +│ └─────────────────────────────────┘ │ └─────────────────────────────────┘ │ +│ │ │ +│ METRICS │ AUDIT QUEUE │ +│ ┌─────────────────────────────────┐ │ ┌─────────────────────────────────┐ │ +│ │ Tasks completed (24h): 12 │ │ │ □ Code review: Feature X │ │ +│ │ Avg completion time: 3.2h │ │ │ □ Doc audit: Module Y │ │ +│ │ Blockers (active): 2 │ │ │ □ Process check: Backend cell │ │ +│ │ Communication volume: 847 msgs │ │ │ │ │ +│ └─────────────────────────────────┘ │ └─────────────────────────────────┘ │ +│ │ +│ REPORTS │ +│ ┌──────────────────────────────────────────────────────────────────────┐ │ +│ │ [Draft] Weekly Report - Dec 8, 2025 │ │ +│ │ [Sent] Daily Summary - Dec 7, 2025 │ │ +│ │ [Sent] Alert: Quality Issue - Dec 6, 2025 │ │ +│ └──────────────────────────────────────────────────────────────────────┘ │ +│ │ +└─────────────────────────────────────────────────────────────────────────────┘ +``` + +### 9.9 CEO Overview + +``` +┌─────────────────────────────────────────────────────────────────────────────┐ +│ CEO OVERVIEW │ +├─────────────────────────────────────────────────────────────────────────────┤ +│ │ +│ HEALTH STATUS │ +│ ┌─────────────┬─────────────┬─────────────┬─────────────┐ │ +│ │ Backend │ Frontend │ UX/UI │ Board │ │ +│ │ 🟢 OK │ 🟢 OK │ 🟡 SLOW │ 🟢 OK │ │ +│ └─────────────┴─────────────┴─────────────┴─────────────┘ │ +│ │ +│ KEY METRICS │ AUDITOR ALERTS │ +│ ┌──────────────────────────────────────┐ │ ┌───────────────────────────┐ │ +│ │ Velocity (weekly): 45 tasks │ │ │ 🔴 1 urgent │ │ +│ │ Completion rate: 94% │ │ │ 🟡 3 warnings │ │ +│ │ Documentation coverage: 87% │ │ │ Last report: 2h ago │ │ +│ │ Active blockers: 2 │ │ │ │ │ +│ └──────────────────────────────────────┘ │ └───────────────────────────┘ │ +│ │ +│ ROADMAP PROGRESS │ +│ ┌─────────────────────────────────────────────────────────────────────┐ │ +│ │ Q4 2025: ████████████████░░░░░░░░░░░░░░░░ 45% │ │ +│ │ v2.0 Release: ██████████████████░░░░░░░░░ 60% │ │ +│ └─────────────────────────────────────────────────────────────────────┘ │ +│ │ +└─────────────────────────────────────────────────────────────────────────────┘ +``` + +--- + +## 10. Data Models + +### 10.1 Task Model + +```python +class Task: + # Identity + id: UUID + title: str + description: str # Detailed + acceptance_criteria: List[str] # HOW DO WE KNOW IT'S DONE? + + # Status + status: TaskStatus # Enum (see below) + priority: int # 0 = highest + + # Ownership + created_by: AgentID + assigned_to: Optional[AgentID] + team: Team # backend | frontend | ux_ui | board + + # Relationships + parent_task: Optional[TaskID] # For sub-tasks + dependencies: List[TaskID] # Blocked by these + blockers: List[TaskID] # Currently blocking these + + # Timestamps + created_at: datetime + claimed_at: Optional[datetime] + started_at: Optional[datetime] + completed_at: Optional[datetime] + + # Planning + plan: TaskPlan + estimated_complexity: Complexity # low | medium | high + + # Execution + execution_log: ExecutionLog + checkpoints: List[Checkpoint] # Saved states + progress_updates: List[ProgressUpdate] + + # Artifacts + commits: List[CommitRef] + documents: List[DocRef] + outputs: List[FileRef] + + # Documentation + dev_notes: str # Journey notes from dev + qa_notes: Optional[str] # QA feedback + documenter_handoff: Optional[HandoffRequest] + final_documentation: List[DocRef] + + # Review + self_verified: bool + qa_verified: Optional[bool] + auditor_notes: Optional[str] + + +class TaskStatus(Enum): + PENDING = "pending" + CLAIMED = "claimed" + IN_PROGRESS = "in_progress" + BLOCKED = "blocked" + PAUSED = "paused" + VERIFYING = "verifying" + NEEDS_REVISION = "needs_revision" + AWAITING_QA = "awaiting_qa" + AWAITING_DOCUMENTATION = "awaiting_documentation" + COMPLETED = "completed" + CANCELLED = "cancelled" + + +class Complexity(Enum): + LOW = "low" + MEDIUM = "medium" + HIGH = "high" + + +class Team(Enum): + BACKEND = "backend" + FRONTEND = "frontend" + UX_UI = "ux_ui" + BOARD = "board" +``` + +### 10.2 Agent Model + +```python +class Agent: + id: UUID + name: str + role: AgentRole + team: Optional[Team] # None for board members + + # Status + status: AgentStatus # active | idle | offline + current_task: Optional[TaskID] + + # Configuration + model: str # e.g., "claude-3-opus", "local-llama" + system_prompt: str + capabilities: List[str] + + # Permissions + can_notify: bool + channels_access: List[ChannelID] + channels_write: List[ChannelID] + + # Metrics + tasks_completed: int + avg_completion_time: float + quality_score: float + + # Journal + journal_id: UUID + + +class AgentRole(Enum): + CEO = "ceo" + PRODUCT_OWNER = "product_owner" + HEAD_MARKETING = "head_marketing" + AUDITOR = "auditor" + MAIN_PM = "main_pm" + CELL_PM = "cell_pm" + DEVELOPER = "developer" + QA = "qa" + DOCUMENTER = "documenter" + + +class AgentStatus(Enum): + ACTIVE = "active" + IDLE = "idle" + OFFLINE = "offline" +``` + +### 10.3 Session Model + +```python +class Session: + """ + A session groups messages within boundaries. + Sessions can be bounded by time, message count, or content length. + """ + id: UUID # sesh_id + group_id: UUID # Parent group + + # Boundaries (any can trigger session end) + max_time_window: Optional[timedelta] # e.g., 30 minutes + max_message_count: Optional[int] # e.g., 100 messages + max_content_length: Optional[int] # e.g., 50000 characters + + # Timeout configuration + timeout_seconds: int # Inactivity timeout + + # State + status: SessionStatus # active | closed | timed_out + + # Timestamps + started_at: datetime + last_activity_at: datetime + closed_at: Optional[datetime] + + # Statistics + message_count: int + total_content_length: int + + +class SessionStatus(Enum): + ACTIVE = "active" + CLOSED = "closed" + TIMED_OUT = "timed_out" +``` + +### 10.4 Message Model + +```python +class RawStream: + """WebSocket payload - ephemeral""" + connection_id: UUID + agent_id: AgentID + channel_id: UUID + chunk: str # Raw LLM output chunk + timestamp: datetime + + +class ExtractedMessage: + """Processed, stored message""" + id: UUID # msg_id + + # Source & Context + agent_id: AgentID + channel_id: UUID + group_id: UUID + session_id: UUID # sesh_id - links to Session + + # Content + type: MessageType + content: str + content_length: int # Character count + + # Threading + is_reply: bool + reply_to: Optional[MessageID] # Parent message if is_reply + + # Mentions (for in-channel references, NOT notifications) + mentions: List[AgentID] + + # Task Context + task_id: Optional[TaskID] + commit_ref: Optional[str] + + # Metadata + timestamp: datetime + embedding: Vector # For RAG + + # Extraction metadata + confidence: float + raw_excerpt: str + + # Edit tracking (AI can only edit own history) + edited_at: Optional[datetime] + edit_history: List[MessageEdit] # Previous versions + + +class MessageEdit: + """Track edits to messages - agents can only edit their own""" + edited_at: datetime + previous_content: str + edit_reason: Optional[str] + + +class MessageType(Enum): + REASONING = "reasoning" + DIALOGUE = "dialogue" + DECISION = "decision" + ACTION = "action" + BLOCKER = "blocker" + TECHNICAL = "technical" +``` + +### 10.5 Group Model + +```python +class Group: + """ + Role-based group chat container. + Groups hold sessions which hold messages. + Access is controlled by hierarchy/level. + """ + id: UUID # group_id + name: str + channel_id: UUID # Parent channel + + # Access Control + allowed_roles: List[AgentRole] # Role-based access + hierarchy_level: int # 0 = highest (board), 3 = lowest (cell members) + + # Members (derived from roles, but can have explicit additions) + members: List[AgentID] + + # Settings + is_active: bool + created_at: datetime + + # Current Session + active_session_id: Optional[UUID] + + # Session Configuration (defaults for new sessions) + default_session_config: SessionConfig + + +class SessionConfig: + """Configuration for session boundaries""" + max_time_window: Optional[timedelta] + max_message_count: Optional[int] + max_content_length: Optional[int] + timeout_seconds: int +``` + +### 10.6 Notification Model + +```python +class Notification: + id: UUID + type: NotificationType + priority: NotificationPriority + + # Routing + from_agent: AgentID # Must be PM/Board/Auditor + to_agents: List[AgentID] + + # Content + subject: str + body: str + + # Acknowledgment + requires_ack: bool + acked_by: List[AgentID] + acked_at: Dict[AgentID, datetime] + + # Context + related_task: Optional[TaskID] + related_messages: List[MessageID] + + # Timing + timestamp: datetime + expires_at: Optional[datetime] + + +class NotificationType(Enum): + TASK_ASSIGNMENT = "task_assignment" + PRIORITY_CHANGE = "priority_change" + BLOCKER_ESCALATION = "blocker_escalation" + REVIEW_REQUEST = "review_request" + DOCUMENTATION_REQUEST = "documentation_request" + ALERT = "alert" + BROADCAST = "broadcast" + + +class NotificationPriority(Enum): + NORMAL = "normal" + HIGH = "high" + URGENT = "urgent" +``` + +### 10.7 Channel Model + +```python +class Channel: + id: UUID + name: str + type: ChannelType + + # Access Control + members: List[AgentID] # Who can see + writers: List[AgentID] # Who can write + silent_observers: List[AgentID] # Auditor + + # Settings + is_archived: bool + created_at: datetime + + # Statistics + message_count: int + last_activity: datetime + + +class ChannelType(Enum): + CELL = "cell" # Internal team + CROSS_CELL = "cross_cell" # Coordination + MANAGEMENT = "management" + SPECIAL = "special" # Announcements, all-hands +``` + +### 10.8 Journal Model + +```python +class Journal: + id: UUID + agent_id: AgentID + entries: List[JournalEntry] + + +class JournalEntry: + id: UUID + journal_id: UUID + + # Content + type: JournalEntryType + title: str + content: str + + # Context + task_id: Optional[TaskID] + session_id: Optional[UUID] + + # Metadata + timestamp: datetime + tags: List[str] + embedding: Vector # For search + + +class JournalEntryType(Enum): + TASK_REFLECTION = "task_reflection" + DECISION_LOG = "decision_log" + LEARNING = "learning" + STRUGGLE = "struggle" + GENERAL = "general" +``` + +### 10.9 Handoff Model + +```python +class DocumenterHandoff: + id: UUID + task_id: TaskID + + # From Dev + commits: List[CommitRef] + notes_location: str + key_conversations: List[MessageID] + documentation_needed: List[str] # ["API docs", "README update"] + + # Status + status: HandoffStatus + assigned_to: Optional[AgentID] # Documenter + + # Timestamps + created_at: datetime + claimed_at: Optional[datetime] + completed_at: Optional[datetime] + + +class HandoffStatus(Enum): + PENDING = "pending" + CLAIMED = "claimed" + IN_PROGRESS = "in_progress" + COMPLETED = "completed" +``` + +--- + +## 11. RAG & Knowledge Base + +### 11.1 Architecture + +``` +┌─────────────────────────────────────────────────────────────────────────────┐ +│ RAG ARCHITECTURE │ +└─────────────────────────────────────────────────────────────────────────────┘ + + ┌─────────────────┐ + │ AGENT QUERY │ + │ "How did we │ + │ handle X?" │ + └────────┬────────┘ + │ + ▼ + ┌─────────────────────────┐ + │ OPTIMAL API │ + │ Query Processing │ + └────────────┬────────────┘ + │ + ┌──────────────────┼──────────────────┐ + │ │ │ + ▼ ▼ ▼ + ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ + │ CODE INDEX │ │ DOC INDEX │ │ CONV INDEX │ + │ │ │ │ │ │ + │ • Repositories │ │ • READMEs │ │ • Messages │ + │ • Functions │ │ • API Docs │ │ • Decisions │ + │ • Classes │ │ • Architecture │ │ • Journals │ + │ • Comments │ │ • Guides │ │ • Task Notes │ + └────────┬────────┘ └────────┬────────┘ └────────┬────────┘ + │ │ │ + └───────────────────┼───────────────────┘ + │ + ▼ + ┌─────────────────────────┐ + │ VECTOR DB │ + │ (Qdrant) │ + │ │ + │ Stored on UGREEN NAS │ + └────────────┬────────────┘ + │ + ▼ + ┌─────────────────────────┐ + │ RELEVANT CONTEXT │ + │ Returned to Agent │ + └─────────────────────────┘ +``` + +### 11.2 Indexing Strategy + +**Code Indexing** +- Repository: fastapi-guard, fastapi-guard-web, etc. +- Chunk by: function, class, module +- Metadata: file path, language, last modified, author + +**Documentation Indexing** +- Source: READMEs, /docs folders, wiki +- Chunk by: section, paragraph +- Metadata: doc type, project, version + +**Conversation Indexing** +- Source: Extracted messages from Messaging API +- Chunk by: message or conversation thread +- Metadata: channel, agent, task, timestamp, type + +**Journal Indexing** +- Source: Journal entries +- Chunk by: entry +- Metadata: agent, task, entry type, timestamp + +### 11.3 Embedding Pipeline + +``` +┌─────────────────────────────────────────────────────────────────────────────┐ +│ EMBEDDING PIPELINE │ +└─────────────────────────────────────────────────────────────────────────────┘ + +Source Content + │ + ▼ +┌─────────────────┐ +│ CHUNKING │ +│ │ +│ • Code: AST │ +│ • Docs: Headers │ +│ • Conv: Thread │ +└────────┬────────┘ + │ + ▼ +┌─────────────────┐ +│ EMBEDDING │ +│ (Olares One) │ +│ │ +│ • text-embedding│ +│ • code-embedding│ +└────────┬────────┘ + │ + ▼ +┌─────────────────┐ +│ VECTOR STORE │ +│ (Qdrant/NAS) │ +└─────────────────┘ +``` + +### 11.4 Query Flow + +```python +# Example RAG Query Flow + +async def query_knowledge_base(query: str, context: QueryContext) -> List[Document]: + """ + Query the knowledge base with RAG. + + Args: + query: Natural language query + context: Current task, agent, project context + + Returns: + Relevant documents for context injection + """ + + # 1. Generate query embedding + query_embedding = await embed(query) + + # 2. Search relevant collections + code_results = await vector_db.search( + collection="code", + embedding=query_embedding, + filter={"project": context.project}, + limit=5 + ) + + doc_results = await vector_db.search( + collection="documentation", + embedding=query_embedding, + filter={"project": context.project}, + limit=5 + ) + + conv_results = await vector_db.search( + collection="conversations", + embedding=query_embedding, + filter={"task_id": context.task_id}, + limit=3 + ) + + # 3. Re-rank and deduplicate + combined = rerank(code_results + doc_results + conv_results) + + # 4. Return top results + return combined[:10] +``` + +--- + +## 12. Security & Access Control + +### 12.1 Permission Model + +``` +┌─────────────────────────────────────────────────────────────────────────────┐ +│ ACCESS CONTROL MODEL │ +└─────────────────────────────────────────────────────────────────────────────┘ + +LEVELS: +├─► L0: CEO (full access) +├─► L1: Board (cross-org access) +├─► L2: Main PM (all cells access) +├─► L3: Cell PM (own cell + PM channel) +├─► L4: Cell Members (own cell only) +└─► SPECIAL: Auditor (silent read all) + +RESOURCES: +├─► Channels: read, write, manage +├─► Tasks: view, create, assign, modify, close +├─► Notifications: send, receive +├─► Documents: read, write, publish +├─► Reports: view, create +└─► System: configure, monitor +``` + +### 12.2 Channel Access Matrix + +``` +Channel │ Read Access │ Write Access │ +─────────────────────┼────────────────────┼────────────────────┤ +#backend-cell │ BE Cell, Auditor │ BE Cell │ +#frontend-cell │ FE Cell, Auditor │ FE Cell │ +#uxui-cell │ UX Cell, Auditor │ UX Cell │ +#dev-all │ All Devs, MPM, AU │ All Devs │ +#qa-all │ All QA, MPM, AU │ All QA │ +#pm-all │ All PMs, MPM, AU │ All PMs │ +#doc-all │ All Docs, MPM, AU │ All Docs │ +#main-pm-board │ MPM, Board, AU │ MPM, Board │ +#board-private │ Board, CEO, AU │ Board, CEO │ +#announcements │ Everyone │ Board, MPM │ +#all-hands │ Everyone │ Everyone │ +``` + +### 12.3 Task Permission Matrix + +``` +Action │ CEO │ Board │ MPM │ PM │ Dev │ QA │ Doc │ +─────────────────────┼─────┼───────┼─────┼─────┼─────┼─────┼─────┤ +View all tasks │ ✓ │ ✓ │ ✓ │ ○ │ ○ │ ○ │ ○ │ +Create task │ ✓ │ ✓ │ ✓ │ ✓ │ ○ │ │ │ +Assign task │ ✓ │ ✓ │ ✓ │ ✓ │ │ │ │ +Claim task │ │ │ │ │ ✓ │ ✓ │ ✓ │ +Update own task │ │ │ │ │ ✓ │ ✓ │ ✓ │ +Close task │ ✓ │ ✓ │ ✓ │ ✓ │ ✓ │ │ │ +Change priority │ ✓ │ ✓ │ ✓ │ ✓ │ │ │ │ + +✓ = Full access +○ = Own cell/tasks only +``` + +### 12.4 Notification Permission Matrix + +``` +Sender │ Recipients │ +─────────────────────┼─────────────────────────────────────────┤ +Cell PM │ Own cell members only │ +Main PM │ All PMs, any cell (escalation) │ +Product Owner │ Main PM, Board │ +Head Marketing │ Main PM, Board │ +Auditor │ Anyone (special privilege) + CEO │ +CEO │ Anyone │ +Dev/QA/Documenter │ Cannot send notifications │ +``` + +--- + +## 13. Implementation Roadmap + +### 13.1 Phase Overview + +``` +┌─────────────────────────────────────────────────────────────────────────────┐ +│ IMPLEMENTATION PHASES │ +└─────────────────────────────────────────────────────────────────────────────┘ + +Phase 0: Foundation (Weeks 1-2) +├─► Hardware setup (Olares One integration) +├─► Base infrastructure (Docker, networking) +└─► Development environment + +Phase 1: Core Services (Weeks 3-6) +├─► Messaging API (basic) +├─► Task management system +└─► Agent orchestration prototype + +Phase 2: Communication (Weeks 7-10) +├─► WebSocket implementation +├─► Transcription pipeline +├─► Notification system + +Phase 3: Intelligence (Weeks 11-14) +├─► Optimal API (RAG) +├─► Journal API +└─► Knowledge base indexing + +Phase 4: Agents (Weeks 15-20) +├─► Agent definitions +├─► Workflow implementation +├─► Cell deployment + +Phase 5: Management (Weeks 21-24) +├─► Kanban interfaces +├─► Auditor dashboard +├─► CEO overview + +Phase 6: Polish (Weeks 25+) +├─► Performance tuning +├─► Documentation +└─► Continuous improvement +``` + +### 13.2 Phase 0: Foundation + +**Goals:** +- Olares One operational +- Network configured +- Base services running + +**Tasks:** +``` +□ Receive and setup Olares One +□ Configure network (static IP, DNS) +□ Install Docker and Docker Compose +□ Setup development environment +□ Configure NAS integration +□ Test GPU capabilities +□ Benchmark model inference +``` + +**Deliverables:** +- Working Olares One with GPU access +- Docker environment ready +- Network topology documented + +### 13.3 Phase 1: Core Services + +**Goals:** +- Basic messaging between agents +- Task CRUD operations +- Simple agent spawning + +**Tasks:** +``` +□ Design database schema +□ Implement Messaging API (REST) +□ Implement Task API +□ Create agent base class +□ Build simple orchestrator +□ Setup PostgreSQL +□ Setup Redis (for queues) +``` + +**Deliverables:** +- Messaging API v0.1 +- Task API v0.1 +- Agent framework v0.1 + +### 13.4 Phase 2: Communication + +**Goals:** +- Real-time communication +- Message extraction +- Formal notifications + +**Tasks:** +``` +□ Implement WebSocket server +□ Build transcription service +□ Create message extraction pipeline +□ Implement notification system +□ Add channel management +□ Build permission system +``` + +**Deliverables:** +- WebSocket streaming +- Transcription pipeline +- Notification API + +### 13.5 Phase 3: Intelligence + +**Goals:** +- RAG operational +- Knowledge base populated +- Agents can query context + +**Tasks:** +``` +□ Setup Qdrant on NAS +□ Build embedding pipeline +□ Index existing repositories +□ Implement Optimal API +□ Implement Journal API +□ Create query interface +``` + +**Deliverables:** +- Optimal API v0.1 +- Journal API v0.1 +- Indexed knowledge base + +### 13.6 Phase 4: Agents + +**Goals:** +- All agent types defined +- Workflows implemented +- Cells operational + +**Tasks:** +``` +□ Define agent prompts per role +□ Implement Dev workflow +□ Implement QA workflow +□ Implement Documenter workflow +□ Implement PM workflows +□ Implement Board workflows +□ Implement Auditor workflow +□ Deploy Backend cell +□ Deploy Frontend cell +□ Deploy UX/UI cell +``` + +**Deliverables:** +- 17 operational agents +- 3 functioning cells +- Working Board + +### 13.7 Phase 5: Management + +**Goals:** +- Visual management tools +- Auditor capabilities +- CEO visibility + +**Tasks:** +``` +□ Build Kanban interfaces +□ Create Auditor dashboard +□ Create CEO overview +□ Implement metrics collection +□ Build reporting system +``` + +**Deliverables:** +- Management UI +- Reporting system +- Metrics dashboard + +### 13.8 Phase 6: Polish + +**Goals:** +- Production ready +- Documented +- Optimized + +**Tasks:** +``` +□ Performance optimization +□ Error handling improvements +□ Documentation completion +□ Testing suite +□ Monitoring and alerting +□ Backup procedures +``` + +**Deliverables:** +- Production-ready system +- Complete documentation +- Operational runbooks + +--- + +## 14. Development Standards & Best Practices + +### 14.1 Universal Principles + +These principles apply to ALL agents, regardless of tech stack: + +``` +┌─────────────────────────────────────────────────────────────────────────────┐ +│ UNIVERSAL DEVELOPMENT PRINCIPLES │ +└─────────────────────────────────────────────────────────────────────────────┘ + +1. NO WORK WITHOUT A TASK + └─► Everything tracked, everything documented + +2. TEST BEFORE COMMIT + └─► All code must pass tests before any commit + +3. LINT AND FORMAT + └─► Code must pass linting/formatting checks + +4. TYPE SAFETY + └─► Use type hints (Python) or TypeScript strictly + +5. DOCUMENT AS YOU GO + └─► Comments, docstrings, inline documentation + +6. SECURITY FIRST + └─► Validate inputs, sanitize outputs, defensive coding + +7. CLEAN COMMITS + └─► Meaningful messages, atomic changes, linked to tasks + +8. PEER REVIEW + └─► QA reviews all work before closure + +9. KNOWLEDGE CAPTURE + └─► Learnings go to knowledge base, not just memory +``` + +### 14.2 Stack-Specific Standards + +#### Python (Backend) + +```yaml +python_standards: + package_manager: uv # Fast, modern + formatter: ruff format + linter: ruff check + type_checker: mypy + test_runner: pytest + min_coverage: 80% + + workflow: + before_commit: + - uv run ruff format . + - uv run ruff check . + - uv run mypy src/ + - uv run pytest + + conventions: + - Use type hints everywhere + - Pydantic for data validation + - Async/await for I/O operations + - Docstrings (Google style) + - Keep functions < 50 lines + - Keep files < 500 lines +``` + +#### TypeScript/React (Frontend) + +```yaml +typescript_standards: + package_manager: pnpm # Or npm/yarn + formatter: prettier + linter: eslint + type_checker: tsc --noEmit + test_runner: vitest # Or jest + min_coverage: 80% + + workflow: + before_commit: + - pnpm format + - pnpm lint + - pnpm typecheck + - pnpm test + + conventions: + - Strict TypeScript (no any) + - Functional components with hooks + - Props interfaces defined + - JSDoc for complex functions + - Component files < 300 lines + - Custom hooks for logic extraction +``` + +#### UX/UI Design + +```yaml +uxui_standards: + design_tool: Figma # Or similar + component_library: Document all components + handoff_format: Specs + assets exported + + workflow: + before_handoff: + - Component specs documented + - All states covered (hover, active, disabled, error) + - Responsive breakpoints defined + - Accessibility notes included + - Assets exported (SVG, PNG as needed) + + conventions: + - Design tokens for colors, spacing, typography + - Component naming matches code + - Annotate interactions + - Document edge cases + - Mobile-first approach +``` + +### 14.3 Git Workflow + +``` +┌─────────────────────────────────────────────────────────────────────────────┐ +│ GIT WORKFLOW │ +└─────────────────────────────────────────────────────────────────────────────┘ + +BRANCH NAMING: +├─► feature/{task-id}-{description} # New features +├─► fix/{task-id}-{description} # Bug fixes +├─► refactor/{task-id}-{description} # Code improvements +├─► docs/{task-id}-{description} # Documentation +└─► hotfix/{task-id}-{description} # Urgent production fixes + +COMMIT MESSAGE FORMAT: +┌─────────────────────────────────────────────────────────────────┐ +│ {type}({scope}): {description} │ +│ │ +│ {body - what and why} │ +│ │ +│ Task: {task-id} │ +│ Co-authored-by: {agent-name} │ +└─────────────────────────────────────────────────────────────────┘ + +TYPES: +├─► feat # New feature +├─► fix # Bug fix +├─► docs # Documentation +├─► style # Formatting (no code change) +├─► refactor # Code restructuring +├─► test # Adding tests +├─► chore # Maintenance tasks +└─► perf # Performance improvements + +EXAMPLE: +feat(auth): add rate limiting to login endpoint + +Implements sliding window rate limiting for login attempts. +Uses Redis for distributed counting across instances. +Limits: 5 attempts per minute, 20 per hour. + +Task: TASK-042 +Co-authored-by: BE-Dev-1 +``` + +### 14.4 Code Review Checklist + +Every QA agent uses this checklist: + +```markdown +## Code Review Checklist + +### Functionality +- [ ] Code does what the task requires +- [ ] Edge cases handled +- [ ] Error states handled gracefully +- [ ] No regressions introduced + +### Code Quality +- [ ] Follows project conventions +- [ ] No code duplication +- [ ] Functions/methods are focused (single responsibility) +- [ ] Naming is clear and consistent +- [ ] No dead code or commented-out code + +### Type Safety +- [ ] All types properly defined +- [ ] No `any` types (TypeScript) or missing hints (Python) +- [ ] Null/undefined handled properly + +### Testing +- [ ] Tests exist for new functionality +- [ ] Tests cover happy path and error cases +- [ ] Tests are readable and maintainable +- [ ] All tests pass + +### Security +- [ ] Inputs validated +- [ ] No sensitive data exposed +- [ ] Authentication/authorization correct +- [ ] No SQL injection, XSS, etc. + +### Performance +- [ ] No obvious performance issues +- [ ] Database queries optimized +- [ ] No N+1 query problems +- [ ] Appropriate caching considered + +### Documentation +- [ ] Public APIs documented +- [ ] Complex logic explained +- [ ] README updated if needed +- [ ] Changelog entry added +``` + +### 14.5 Automated Quality Gates + +``` +┌─────────────────────────────────────────────────────────────────────────────┐ +│ AUTOMATED QUALITY GATES │ +└─────────────────────────────────────────────────────────────────────────────┘ + +GATE 1: PRE-COMMIT (Agent-side) +┌─────────────────────────────────────────────────────────────────┐ +│ Triggered: Before any commit │ +│ Blocking: Yes │ +│ │ +│ Python: │ +│ └─► ruff format --check │ +│ └─► ruff check │ +│ └─► mypy │ +│ │ +│ TypeScript: │ +│ └─► prettier --check │ +│ └─► eslint │ +│ └─► tsc --noEmit │ +└─────────────────────────────────────────────────────────────────┘ + +GATE 2: PRE-PUSH (Agent-side) +┌─────────────────────────────────────────────────────────────────┐ +│ Triggered: Before pushing to remote │ +│ Blocking: Yes │ +│ │ +│ All stacks: │ +│ └─► Run full test suite │ +│ └─► Check test coverage >= threshold │ +│ └─► Verify no secrets in code │ +└─────────────────────────────────────────────────────────────────┘ + +GATE 3: CI/CD (System-side) +┌─────────────────────────────────────────────────────────────────┐ +│ Triggered: On pull request │ +│ Blocking: Yes │ +│ │ +│ All stacks: │ +│ └─► Full lint/format/type check │ +│ └─► Full test suite (all Python versions if applicable) │ +│ └─► Security vulnerability scan │ +│ └─► Dependency audit │ +│ └─► Build verification │ +└─────────────────────────────────────────────────────────────────┘ + +GATE 4: QA REVIEW (Human/Agent) +┌─────────────────────────────────────────────────────────────────┐ +│ Triggered: When task marked "ready for review" │ +│ Blocking: Yes │ +│ │ +│ └─► Code review checklist │ +│ └─► Manual/exploratory testing │ +│ └─► Acceptance criteria verification │ +└─────────────────────────────────────────────────────────────────┘ +``` + +--- + +## 15. Task Management & Context Persistence + +### 15.1 The Problem + +AI agents have context limits. Sessions end. Memory is imperfect. Without proper task management: +- Work gets lost between sessions +- Agents repeat analysis unnecessarily +- Knowledge isn't captured for future use +- Handoffs between agents fail + +### 15.2 The Solution: Structured Task Records + +Every task creates a persistent record that: +- Survives session boundaries +- Enables clean handoffs between agents +- Builds project knowledge over time +- Reduces redundant work + +### 15.3 Task Directory Structure + +``` +.tasks/ +├── index.md # Master index of all tasks +├── templates/ # Task templates by type +│ ├── feature.md +│ ├── bugfix.md +│ ├── research.md +│ └── documentation.md +│ +├── active/ # Currently in-progress tasks +│ ├── TASK-042-auth-rate-limiting/ +│ │ ├── README.md # Task overview & status +│ │ ├── requirements.md # Detailed requirements +│ │ ├── plan.md # Implementation plan +│ │ ├── journal.md # Agent journey notes +│ │ ├── findings.md # Analysis & discoveries +│ │ ├── decisions.md # Decisions made & rationale +│ │ ├── blockers.md # Current blockers (if any) +│ │ ├── handoff.md # Handoff notes for Documenter +│ │ ├── qa-review.md # QA feedback +│ │ └── artifacts/ # Code samples, diagrams, etc. +│ │ ├── code-samples/ +│ │ └── diagrams/ +│ │ +│ └── TASK-043-dashboard-redesign/ +│ └── ... +│ +├── completed/ # Finished tasks (archived) +│ ├── 2025-12/ # Organized by month +│ │ ├── TASK-038-fix-memory-leak/ +│ │ └── TASK-039-add-dark-mode/ +│ └── 2025-11/ +│ └── ... +│ +└── blocked/ # Tasks waiting on blockers + └── TASK-040-integration-api/ + └── ... +``` + +### 15.4 Task Record Templates + +#### README.md (Required) + +```markdown +# TASK-{id}: {title} + +## Status +- **State**: {pending | in_progress | blocked | review | documenting | completed} +- **Priority**: {P0 | P1 | P2 | P3} +- **Assigned To**: {agent-id} +- **Cell**: {backend | frontend | ux_ui} + +## Dates +- **Created**: YYYY-MM-DD +- **Started**: YYYY-MM-DD +- **Target**: YYYY-MM-DD +- **Completed**: YYYY-MM-DD + +## Overview +{Brief description of what this task accomplishes} + +## Acceptance Criteria +- [ ] Criterion 1 +- [ ] Criterion 2 +- [ ] Criterion 3 + +## Dependencies +- Blocked by: {TASK-XXX, TASK-YYY} +- Blocks: {TASK-ZZZ} + +## Key Files +- `path/to/main/file.py` +- `path/to/test/file.py` + +## Commits +- `abc1234` - Initial implementation +- `def5678` - Added tests +- `ghi9012` - Fixed edge case + +## Quick Context Restore +{2-3 sentences an agent can read to immediately understand the task state} +``` + +#### plan.md + +```markdown +# Implementation Plan: TASK-{id} + +## Approach +{High-level approach description} + +## Sub-Tasks +- [ ] 1. {Sub-task 1} + - Estimated: {time} + - Notes: {any notes} +- [ ] 2. {Sub-task 2} +- [ ] 3. {Sub-task 3} + +## Technical Considerations +- {Consideration 1} +- {Consideration 2} + +## Risks +- {Risk 1}: {Mitigation} +- {Risk 2}: {Mitigation} + +## Open Questions +- [ ] {Question 1} +- [x] {Question 2} → Answer: {answer} +``` + +#### journal.md + +```markdown +# Agent Journey: TASK-{id} + +## Session 1 - YYYY-MM-DD HH:MM +**Agent**: {agent-id} + +### What I Did +- Analyzed the requirements +- Explored the codebase around X +- Identified approach Y + +### What I Learned +- The existing system does Z because... +- There's a related implementation in... + +### What I Struggled With +- Understanding the async flow in... +- The documentation for X was unclear + +### Next Steps +- [ ] Implement the core logic +- [ ] Add error handling + +--- + +## Session 2 - YYYY-MM-DD HH:MM +**Agent**: {agent-id} + +### What I Did +... +``` + +#### decisions.md + +```markdown +# Decisions Log: TASK-{id} + +## Decision 1: {Title} +**Date**: YYYY-MM-DD +**Decider**: {agent-id} + +### Context +{What situation required a decision} + +### Options Considered +1. **Option A**: {description} + - Pros: ... + - Cons: ... +2. **Option B**: {description} + - Pros: ... + - Cons: ... + +### Decision +Chose **Option A** because... + +### Consequences +- We will need to... +- This means... + +--- + +## Decision 2: {Title} +... +``` + +#### handoff.md (For Documenter) + +```markdown +# Documentation Handoff: TASK-{id} + +## Summary +{What was built, in plain language} + +## Documentation Needed +- [ ] API documentation for new endpoints +- [ ] README update for new feature +- [ ] Architecture doc update +- [ ] Changelog entry + +## Key Commits +| Commit | Description | +|--------|-------------| +| abc1234 | Main implementation | +| def5678 | Tests | + +## Important Conversations +- Message ID: {id} - Discussion about approach +- Message ID: {id} - Decision on X + +## Dev Notes Location +See `journal.md` for full journey notes. + +## Gotchas for Documentation +- Make sure to mention X limitation +- The Y parameter is optional but important because... + +## Code Samples to Include +```python +# Example usage +from module import feature +result = feature.do_thing(param) +``` +``` + +### 15.5 Context Restoration Protocol + +When an agent picks up a task (especially one they didn't start): + +``` +┌─────────────────────────────────────────────────────────────────────────────┐ +│ CONTEXT RESTORATION PROTOCOL │ +└─────────────────────────────────────────────────────────────────────────────┘ + +STEP 1: READ TASK RECORD +┌─────────────────────────────────────────────────────────────────┐ +│ Required reading (in order): │ +│ 1. README.md → Current state, criteria, quick context │ +│ 2. plan.md → What's the approach, what's left │ +│ 3. journal.md → What happened, what was learned │ +│ 4. decisions.md → Why things are the way they are │ +│ 5. blockers.md → Any current impediments │ +└─────────────────────────────────────────────────────────────────┘ + +STEP 2: REVIEW ARTIFACTS +┌─────────────────────────────────────────────────────────────────┐ +│ If applicable: │ +│ • Code samples in artifacts/ │ +│ • Related commits (git log) │ +│ • Test files │ +└─────────────────────────────────────────────────────────────────┘ + +STEP 3: CHECK RELATED CONTEXT +┌─────────────────────────────────────────────────────────────────┐ +│ Query knowledge base: │ +│ • Similar past tasks │ +│ • Related documentation │ +│ • Relevant conversation history │ +└─────────────────────────────────────────────────────────────────┘ + +STEP 4: ACKNOWLEDGE STATE +┌─────────────────────────────────────────────────────────────────┐ +│ Before starting work, add to journal: │ +│ "Resuming task. Context restored from records." │ +│ "Last state: {summary}" │ +│ "My plan: {what I'll do now}" │ +└─────────────────────────────────────────────────────────────────┘ +``` + +### 15.6 Task Indexing + +The master index for quick lookup: + +```markdown +# Task Index + +## Active Tasks + +| ID | Title | Cell | Assigned | Priority | State | Updated | +|----|-------|------|----------|----------|-------|---------| +| TASK-042 | Auth Rate Limiting | Backend | BE-Dev-1 | P1 | in_progress | 2025-12-08 | +| TASK-043 | Dashboard Redesign | Frontend | FE-Dev-2 | P2 | review | 2025-12-08 | +| TASK-044 | New Logo | UX/UI | UX-Dev | P2 | in_progress | 2025-12-07 | + +## Blocked Tasks + +| ID | Title | Blocked By | Since | +|----|-------|------------|-------| +| TASK-040 | Integration API | TASK-042 | 2025-12-05 | + +## Recently Completed + +| ID | Title | Completed | Duration | +|----|-------|-----------|----------| +| TASK-039 | Dark Mode | 2025-12-06 | 3 days | +| TASK-038 | Memory Leak Fix | 2025-12-04 | 1 day | + +## Statistics +- Active: 3 +- Blocked: 1 +- Completed (this month): 12 +- Avg completion time: 2.3 days +``` + +### 15.7 Knowledge Capture Workflow + +``` +┌─────────────────────────────────────────────────────────────────────────────┐ +│ KNOWLEDGE CAPTURE WORKFLOW │ +└─────────────────────────────────────────────────────────────────────────────┘ + +During Task Execution: +┌─────────────────────────────────────────────────────────────────┐ +│ Agent captures in journal.md: │ +│ • What they tried │ +│ • What worked / didn't work │ +│ • Why certain approaches were chosen │ +│ • Gotchas discovered │ +└─────────────────────────────────────────────────────────────────┘ + │ + ▼ +On Task Completion: +┌─────────────────────────────────────────────────────────────────┐ +│ Dev extracts to handoff.md: │ +│ • Key learnings │ +│ • Patterns used │ +│ • Pitfalls to avoid │ +│ • Reusable solutions │ +└─────────────────────────────────────────────────────────────────┘ + │ + ▼ +Documenter Processing: +┌─────────────────────────────────────────────────────────────────┐ +│ Documenter creates: │ +│ • User-facing documentation │ +│ • Developer documentation │ +│ • Knowledge base articles (if applicable) │ +│ • Best practices updates (if applicable) │ +└─────────────────────────────────────────────────────────────────┘ + │ + ▼ +RAG Indexing: +┌─────────────────────────────────────────────────────────────────┐ +│ Optimal API indexes: │ +│ • Task records (for "how did we solve X before?") │ +│ • New documentation │ +│ • Decision rationales │ +│ • Code patterns │ +└─────────────────────────────────────────────────────────────────┘ +``` + +### 15.8 Cross-Session State Management + +``` +┌─────────────────────────────────────────────────────────────────────────────┐ +│ CROSS-SESSION STATE MANAGEMENT │ +└─────────────────────────────────────────────────────────────────────────────┘ + +STATE THAT MUST PERSIST: +├─► Task status and progress +├─► Implementation plan (remaining sub-tasks) +├─► Decisions made and rationale +├─► Blockers and their status +├─► Commits linked to task +├─► Conversation references +├─► Journal entries +└─► Handoff information + +STATE STORAGE LOCATIONS: +┌─────────────────────────────────────────────────────────────────┐ +│ Source of Truth: │ +│ • .tasks/ directory → Task records (markdown) │ +│ • PostgreSQL → Task metadata, status, relations │ +│ • Git → Code changes, commits │ +│ • Vector DB → Searchable embeddings │ +│ │ +│ Ephemeral (session only): │ +│ • Agent working memory → Current context window │ +│ • Redis → Active session state │ +└─────────────────────────────────────────────────────────────────┘ + +SYNC PROTOCOL: +┌─────────────────────────────────────────────────────────────────┐ +│ On session start: │ +│ 1. Load task record from .tasks/ │ +│ 2. Verify against PostgreSQL │ +│ 3. Load relevant context from Vector DB │ +│ │ +│ During session: │ +│ 1. Update journal.md incrementally │ +│ 2. Save checkpoints to .tasks/ regularly │ +│ 3. Update PostgreSQL on state changes │ +│ │ +│ On session end: │ +│ 1. Final save to .tasks/ │ +│ 2. Update PostgreSQL status │ +│ 3. Index new content to Vector DB │ +│ 4. Clear Redis session state │ +└─────────────────────────────────────────────────────────────────┘ +``` + +--- + +## 16. Agent Capabilities & Commands + +### 16.1 Specialized Agent Capabilities + +Beyond their primary roles, agents can invoke specialized capabilities: + +``` +┌─────────────────────────────────────────────────────────────────────────────┐ +│ SPECIALIZED CAPABILITIES │ +└─────────────────────────────────────────────────────────────────────────────┘ + +CODE QUALITY & REVIEW +├─► code-review Deep code review with quality, security, maintainability +├─► architecture Architectural consistency, SOLID principles +├─► python-expert Advanced Python, async/await, performance +└─► typescript-expert Advanced TypeScript, React patterns, hooks + +SECURITY & PERFORMANCE +├─► security-audit Vulnerability review, OWASP compliance +├─► api-security REST API security, auth, injection, exposure +├─► performance Profiling, bottlenecks, caching strategies +└─► database-optimize SQL optimization, indexing, N+1 detection + +DEVOPS & INFRASTRUCTURE +├─► devops-debug Production debugging, log analysis, incidents +├─► deployment CI/CD, Docker, Kubernetes, cloud +├─► networking DNS, SSL/TLS, CDN, network security +└─► database-admin Backups, replication, disaster recovery + +DEVELOPMENT SUPPORT +├─► debugger Error resolution, test failures, unexpected behavior +├─► error-detective Log searching, stack traces, root cause +├─► api-architect RESTful design, microservices, schema design +└─► api-documenter OpenAPI specs, SDK generation, dev docs + +RESEARCH & PLANNING +├─► researcher In-depth research with sources and citations +├─► tech-researcher Code repos, API docs, implementations +├─► task-decomposer Breaking complex goals into actionable tasks +└─► context-manager Managing context across multi-agent workflows + +SPECIALIZED +├─► dx-optimizer Developer experience, tooling, workflows +├─► orchestrator Complex multi-step workflow coordination +└─► prompt-engineer Optimizes prompts, expert in prompt patterns +``` + +### 16.2 Available Commands + +Commands are pre-defined workflows agents can invoke: + +``` +┌─────────────────────────────────────────────────────────────────────────────┐ +│ AVAILABLE COMMANDS │ +└─────────────────────────────────────────────────────────────────────────────┘ + +TESTING & QUALITY +├─► /test Run comprehensive test suite +├─► /test-coverage Run tests with coverage report +├─► /lint Run all linters +├─► /typecheck Run type checker +└─► /code-review Automated code review + +SECURITY +├─► /security-audit Comprehensive vulnerability scan +├─► /security-hardening Apply security best practices +├─► /dependency-audit Check for vulnerable dependencies +└─► /secrets-scan Scan for exposed secrets + +DOCUMENTATION +├─► /doc-api Generate API documentation +├─► /doc-architecture Create architecture docs +├─► /doc-update Update existing documentation +└─► /changelog Add changelog entry + +DEVELOPMENT +├─► /debug Debug complex errors with analysis +├─► /refactor Code refactoring with patterns +├─► /optimize Performance optimization +└─► /cleanup Code cleanup and dead code removal + +PROJECT +├─► /status Current task status +├─► /context Load context for current task +├─► /handoff Prepare handoff documentation +├─► /checkpoint Save current state +└─► /complete Mark task complete, trigger handoff + +META +├─► /help Show available commands +├─► /capabilities Show available capabilities +└─► /think Deep analysis mode +``` + +### 16.3 Tool Integrations (MCP Servers) + +``` +┌─────────────────────────────────────────────────────────────────────────────┐ +│ TOOL INTEGRATIONS │ +└─────────────────────────────────────────────────────────────────────────────┘ + +DEVELOPMENT TOOLS +├─► context7 Library documentation retrieval +├─► sequential-thinking Problem-solving and analysis +├─► task-manager Task and workflow management +└─► kanban Project board management + +INFRASTRUCTURE +├─► docker Container operations and management +├─► postgresql Database operations +├─► redis Cache and queue operations +└─► filesystem File operations in allowed directories + +EXTERNAL SERVICES +├─► fetch Web content fetching +├─► notion Notion workspace integration +├─► slack Slack messaging and notifications +└─► github Repository operations + +AI/ML +├─► rag-query Query knowledge base +├─► embeddings Generate embeddings +└─► prompt-optimize Optimize prompts for efficiency +``` + +### 16.4 Quality Hooks + +Hooks that run automatically at specific points: + +```yaml +hooks: + post_edit: + python: + - command: "ruff format {file}" + description: "Auto-format Python files" + - command: "ruff check {file}" + description: "Lint Python files" + blocking: true + - command: "mypy {file}" + description: "Type check Python files" + blocking: true + + typescript: + - command: "prettier --write {file}" + description: "Auto-format TypeScript files" + - command: "eslint {file}" + description: "Lint TypeScript files" + blocking: true + + pre_commit: + - command: "run-tests --changed" + description: "Run tests for changed files" + blocking: true + + post_commit: + - command: "update-task-status" + description: "Update task with commit reference" + + session_end: + - command: "save-checkpoint" + description: "Save task state" + - command: "update-journal" + description: "Finalize journal entry" +``` + +--- + +## Appendix A: Technology Stack + +### Infrastructure +| Component | Technology | +|-----------|------------| +| Container Runtime | Docker + Docker Compose | +| Orchestration | Custom Python (Phase 1), K3s (future) | +| Database | PostgreSQL | +| Cache/Queue | Redis | +| Vector DB | Qdrant | +| Object Storage | MinIO (NAS) | + +### Backend Services +| Service | Technology | +|---------|------------| +| API Framework | FastAPI | +| WebSockets | FastAPI + websockets | +| Task Queue | Celery / Redis Streams | +| ORM | SQLAlchemy / Tortoise | + +### AI/ML +| Component | Technology | +|-----------|------------| +| Cloud LLM | Claude API (Anthropic) | +| Local LLM | Ollama / vLLM | +| Embeddings | text-embedding-3-small / local | +| Agent Framework | Claude Code / Custom | + +### Frontend (Future) +| Component | Technology | +|-----------|------------| +| Framework | React / Next.js | +| UI Library | TBD | +| Real-time | WebSocket client | + +--- + +## Appendix B: Glossary + +| Term | Definition | +|------|------------| +| **Agent** | An AI instance with a defined role, running on the Olares One | +| **Cell** | A team unit (Backend, Frontend, UX/UI) with Devs, QA, PM, Documenter | +| **Channel** | A communication space where agents stream and chat | +| **Communication** | Constant stream of agent activity (reasoning, dialogue, actions) | +| **Documenter** | Agent responsible for creating production documentation | +| **Journal** | Personal log maintained by each agent | +| **Notification** | Formal signal requiring acknowledgment | +| **Optimal API** | Service providing RAG, prompt optimization, token management | +| **RAG** | Retrieval-Augmented Generation - querying knowledge base for context | +| **Session** | Group of messages within time/count boundaries | +| **Task** | Atomic unit of work, wrapped in the universal lifecycle | +| **Transcription** | Process of extracting structured messages from agent streams | + +--- + +## Appendix C: Configuration Templates + +### Agent Configuration Example + +```yaml +agent: + id: "be-dev-1" + name: "Backend Developer 1" + role: developer + team: backend + + model: + provider: anthropic + name: claude-3-opus + fallback: local-llama-70b + + system_prompt: | + You are a senior backend developer working on the FastAPI Guard ecosystem. + You follow the task lifecycle strictly and document your journey. + You communicate constantly in your cell channel. + You ask questions when unclear. + + capabilities: + - code_execution + - git_operations + - file_management + - web_search + + permissions: + can_notify: false + channels_read: + - backend-cell + - dev-all + - announcements + - all-hands + channels_write: + - backend-cell + - dev-all + - all-hands +``` + +### Channel Configuration Example + +```yaml +channel: + id: "backend-cell" + name: "#backend-cell" + type: cell + team: backend + + members: + - be-dev-1 + - be-dev-2 + - be-qa + - be-pm + - be-doc + + silent_observers: + - auditor + + settings: + message_retention_days: 90 + max_message_length: 10000 + allow_threads: true + allow_reactions: true +``` + +--- + +## Appendix D: API Endpoint Summary + +### Messaging API +``` +# Channels +POST /api/v1/channels # Create channel (admin) +GET /api/v1/channels # List accessible channels +GET /api/v1/channels/{id} # Get channel details + +# Groups (within channels) +POST /api/v1/channels/{id}/groups # Create group +GET /api/v1/channels/{id}/groups # List groups in channel +GET /api/v1/groups/{id} # Get group details +PUT /api/v1/groups/{id} # Update group settings + +# Sessions (within groups) +POST /api/v1/groups/{id}/sessions # Create session +GET /api/v1/groups/{id}/sessions # List sessions in group +GET /api/v1/sessions/{id} # Get session with messages +PUT /api/v1/sessions/{id}/close # Close session manually + +# Messages (within sessions) +POST /api/v1/sessions/{id}/messages # Send message +GET /api/v1/sessions/{id}/messages # Get messages (paginated) +GET /api/v1/messages/{id} # Get specific message +PUT /api/v1/messages/{id} # Edit own message +DELETE /api/v1/messages/{id} # Delete own message + +# WebSocket (real-time) +WS /api/v1/ws/channels/{id} # Channel stream +WS /api/v1/ws/groups/{id} # Group stream +WS /api/v1/ws/agents/{id} # Agent's output stream + +# Notifications +POST /api/v1/notifications # Send notification +GET /api/v1/notifications # Get pending notifications +PUT /api/v1/notifications/{id}/ack # Acknowledge notification + +# Search +GET /api/v1/search/messages # Search messages (full-text) +GET /api/v1/search/sessions # Search sessions +``` + +### Task API +``` +POST /api/v1/tasks +GET /api/v1/tasks +GET /api/v1/tasks/{id} +PUT /api/v1/tasks/{id} +PUT /api/v1/tasks/{id}/claim +PUT /api/v1/tasks/{id}/status +POST /api/v1/tasks/{id}/handoff +GET /api/v1/tasks/kanban/{view} +``` + +### Optimal API +``` +POST /api/v1/kb/index +GET /api/v1/kb/search +POST /api/v1/rag/query +POST /api/v1/prompts +GET /api/v1/prompts/{id} +POST /api/v1/prompts/{id}/render +POST /api/v1/tokens/estimate +``` + +### Journal API +``` +GET /api/v1/journals/{agent_id} +POST /api/v1/journals/{agent_id}/entries +GET /api/v1/journals/{agent_id}/entries +GET /api/v1/journals/{agent_id}/summary +``` + +--- + +## Document History + +| Version | Date | Author | Changes | +|---------|------|--------|---------| +| 1.0.0 | Dec 2025 | Renzo Franceschini | Initial blueprint | + +--- + +*This document serves as the foundational blueprint for the AI Agents Company project. It should be updated as the project evolves and new requirements emerge.* \ No newline at end of file diff --git a/docs/MONET_SUMMARY.md b/docs/MONET_SUMMARY.md new file mode 100644 index 00000000..4f2dd604 --- /dev/null +++ b/docs/MONET_SUMMARY.md @@ -0,0 +1,1656 @@ +# RoboCo & Codepanion: Strategic Summary + +**Date:** December 23, 2024 +**Context:** Strategic planning session covering architecture analysis, monetization, product strategy, company formation, open source strategy, platform evolution, and organizational workflow. + +--- + +## Table of Contents + +1. [RoboCo Architecture Deep Dive](#1-roboco-architecture-deep-dive) +2. [What Makes RoboCo Valuable](#2-what-makes-roboco-valuable) +3. [Monetization Strategy](#3-monetization-strategy) +4. [Target Market Analysis](#4-target-market-analysis) +5. [Product Split: RoboCo vs Codepanion](#5-product-split-roboco-vs-codepanion) +6. [Open Source Strategy](#6-open-source-strategy) +7. [Platform Architecture Options](#7-platform-architecture-options) +8. [API-First Architecture Initiative](#8-api-first-architecture-initiative) +9. [Organizational Workflow](#9-organizational-workflow) +10. [Codepanion Technical Specification](#10-codepanion-technical-specification) +11. [Company Formation Options](#11-company-formation-options) +12. [Go-to-Market Strategy](#12-go-to-market-strategy) +13. [Honest Assessment](#13-honest-assessment) +14. [Next Steps & Action Items](#14-next-steps--action-items) + +--- + +## 1. RoboCo Architecture Deep Dive + +### 1.1 Core Identity + +RoboCo is **not** a RAG system or simple multi-agent chatbot. It is a **full AI workforce orchestration platform** — a complete implementation of an autonomous AI development team with hierarchy, workflow management, quality gates, and persistent state. + +### 1.2 Technical Stack + +| Layer | Technology | +|-------|------------| +| **Backend** | FastAPI (Python) | +| **Database** | PostgreSQL with SQLAlchemy ORM | +| **Agent Runtime** | Docker containers running Claude Code | +| **Agent Communication** | MCP (Model Context Protocol) servers | +| **Frontend** | Next.js 14 with TypeScript | +| **State Management** | Zustand | +| **Styling** | Tailwind CSS + shadcn/ui | + +### 1.3 Agent Architecture + +#### Agent Hierarchy + +``` +┌──────────────────────────────────────────────────────────────┐ +│ BOARD │ +│ ┌─────────────┐ ┌──────────────┐ ┌─────────────┐ │ +│ │Product Owner│ │Head Marketing│ │ Auditor │ │ +│ └─────────────┘ └──────────────┘ └─────────────┘ │ +└──────────────────────────────────────────────────────────────┘ + │ + ▼ +┌──────────────────────────────────────────────────────────────┐ +│ MAIN PM │ +│ Coordinates across all cells │ +└──────────────────────────────────────────────────────────────┘ + │ + ┌───────────────────┼───────────────────┐ + ▼ ▼ ▼ +┌───────────────┐ ┌───────────────┐ ┌───────────────┐ +│ BACKEND CELL │ │ FRONTEND CELL │ │ UX/UI CELL │ +├───────────────┤ ├───────────────┤ ├───────────────┤ +│ be-pm │ │ fe-pm │ │ ux-pm │ +│ be-dev-1 │ │ fe-dev-1 │ │ ux-dev │ +│ be-dev-2 │ │ fe-dev-2 │ │ ux-qa │ +│ be-qa │ │ fe-qa │ │ ux-doc │ +│ be-doc │ │ fe-doc │ │ │ +└───────────────┘ └───────────────┘ └───────────────┘ +``` + +#### Agent Roles (from `models/base.py`) + +| Role | Enum Value | Description | +|------|------------|-------------| +| `SYSTEM` | `system` | Internal orchestrator operations | +| `CEO` | `ceo` | Executive oversight | +| `PRODUCT_OWNER` | `product_owner` | Product strategy and approval | +| `HEAD_MARKETING` | `head_marketing` | Marketing tasks | +| `AUDITOR` | `auditor` | Quality oversight, read access to all (the "spy") | +| `MAIN_PM` | `main_pm` | Cross-cell coordination | +| `CELL_PM` | `cell_pm` | Cell-level task management | +| `DEVELOPER` | `developer` | Code execution | +| `QA` | `qa` | Quality assurance | +| `DOCUMENTER` | `documenter` | Documentation | + +### 1.4 Task Lifecycle + +#### Status Flow (from `models/base.py`) + +``` +BACKLOG ──► PENDING ──► CLAIMED ──► IN_PROGRESS ───┬──► BLOCKED + │ │ + │ ▼ + │ (PM resolves) + │ │ + ◄───────┘ + │ + ▼ + AWAITING_QA + │ + ┌──────────────┴──────────────┐ + ▼ ▼ + NEEDS_REVISION (QA passes) + │ │ + └──────► IN_PROGRESS ▼ + AWAITING_DOCUMENTATION + │ + ▼ + AWAITING_PM_REVIEW + │ + ▼ + COMPLETED +``` + +#### Task Statuses Explained + +| Status | Description | +|--------|-------------| +| `BACKLOG` | PM setup phase, session must be created before activation | +| `PENDING` | Ready for work, orchestrator can spawn agents | +| `CLAIMED` | Agent has claimed the task | +| `IN_PROGRESS` | Active development | +| `BLOCKED` | Waiting on external dependency or decision | +| `PAUSED` | Temporarily halted | +| `VERIFYING` | Self-verification in progress | +| `NEEDS_REVISION` | QA rejected, needs fixes | +| `AWAITING_QA` | Ready for QA review | +| `AWAITING_DOCUMENTATION` | QA passed, needs docs | +| `AWAITING_PM_REVIEW` | Docs complete, needs PM sign-off | +| `COMPLETED` | Fully done | +| `CANCELLED` | Abandoned | + +### 1.5 Orchestrator System (`runtime/orchestrator.py`) + +The orchestrator is the **brain** of the system. Key characteristics: + +#### Smart Spawning +- **Checks for work BEFORE spawning agents** (cost-efficient) +- Claims tasks on behalf of agents before spawning +- Agents receive assignment at spawn time +- No wasteful container spawns + +#### Docker-Based Runtime +```python +AGENT_IMAGE = "roboco-agent" +AGENT_NETWORK = "roboco_default" +``` + +Each agent runs as an isolated Docker container with: +- Claude Code as the runtime +- MCP config for tool access +- Blueprint (system prompt) mounted +- Shared Claude auth + +#### Agent States (from `models/runtime.py`) + +| State | Description | +|-------|-------------| +| `IDLE` | Not running | +| `STARTING` | Container spinning up | +| `ACTIVE` | Working on task | +| `WAITING_SHORT` | Brief pause (within container) | +| `WAITING_LONG` | Terminated, will respawn when condition resolves | +| `STOPPING` | Graceful shutdown in progress | +| `OFFLINE` | Container stopped | +| `ERROR` | Failed state | + +#### Task Routing Intelligence + +The orchestrator classifies tasks based on complexity and keywords: + +```python +# Board-level keywords +_BOARD_KEYWORDS = {"roadmap", "architecture", "security", "budget", + "hiring", "strategy", "vision", "milestone", + "release", "launch"} + +# PM coordination keywords +_PM_KEYWORDS = {"coordinate", "integration", "cross-team", "sync", + "planning", "milestone", "dependencies", "review"} +``` + +Routing decision tree: +1. **Board keywords** → `product-owner` +2. **High complexity or cross-team** → `main-pm` +3. **PM keywords or medium complexity** → Cell PM +4. **Low complexity, single team** → Direct to developer + +#### Dispatcher Loop + +The orchestrator runs background dispatchers every 30 seconds: + +1. `_dispatch_pm_work()` — Routes new tasks to appropriate level +2. `_dispatch_pm_closure_work()` — Checks parent tasks ready to close +3. `_dispatch_dev_work()` — Spawns devs for assigned tasks +4. `_dispatch_qa_work()` — Spawns QA for awaiting_qa tasks +5. `_dispatch_doc_work()` — Spawns documenters +6. `_dispatch_pm_review_work()` — Spawns PMs for final review +7. `_dispatch_marketing_work()` — Handles marketing tasks +8. `_dispatch_blocker_work()` — Handles blocked tasks +9. `_dispatch_escalation_work()` — Handles escalations +10. `_dispatch_approval_work()` — Handles approval requests +11. `_dispatch_audit_work()` — Triggers auditor when needed + +### 1.6 MCP Tool System + +Each agent gets access to 4 MCP servers: + +#### roboco-task (`mcp/task_server.py`) +- `roboco_task_scan()` — Find available work +- `roboco_task_get(task_id)` — Get task details +- `roboco_task_claim(task_id)` — Claim a task +- `roboco_task_start(task_id)` — Begin work +- `roboco_task_plan(task_id, ...)` — Create execution plan +- `roboco_task_progress(task_id, %, msg)` — Report progress +- `roboco_task_checkpoint(task_id, ...)` — Save checkpoint +- `roboco_task_block(task_id, reason)` — Mark blocked +- `roboco_task_unblock(task_id)` — Remove block +- `roboco_task_complete(task_id)` — Mark complete +- `roboco_task_create(...)` — Create subtask +- `roboco_task_qa_pass(task_id)` — QA approval +- `roboco_task_qa_fail(task_id, notes)` — QA rejection +- `roboco_task_docs_complete(task_id)` — Documentation done +- `roboco_agent_idle()` — Signal no more work + +#### roboco-message (`mcp/message_server.py`) +- `roboco_message_send(channel, content, type)` — Send message +- `roboco_message_read(channel, limit)` — Read channel history +- `roboco_message_reply(message_id, content)` — Reply to message + +#### roboco-notify (`mcp/notify_server.py`) +- `roboco_notify_send(to, subject, body, priority)` — Send notification +- `roboco_notify_ack(notification_id)` — Acknowledge +- `roboco_escalate(to, subject, body)` — Escalate issue + +#### roboco-journal (`mcp/journal_server.py`) +- `roboco_journal_entry(type, title, content)` — Add journal entry +- `roboco_journal_decision(...)` — Log decision +- `roboco_journal_learning(...)` — Log learning +- `roboco_journal_struggle(...)` — Log struggle + +### 1.7 Communication System + +#### Hierarchy + +``` +Channel (e.g., "backend-cell") + └── Group (e.g., "general", "code-review") + └── Session (scoped discussion) + └── Messages +``` + +#### Channel Types (from `models/base.py`) + +| Type | Description | +|------|-------------| +| `CELL` | Internal team communication | +| `CROSS_CELL` | Coordination between teams | +| `MANAGEMENT` | PM and board communications | +| `SPECIAL` | Announcements, all-hands | + +#### Message Types + +| Type | Description | +|------|-------------| +| `REASONING` | Agent's internal thought process | +| `DIALOGUE` | Normal conversation | +| `DECISION` | Decision announcement | +| `ACTION` | Action taken | +| `BLOCKER` | Blocker announcement | +| `TECHNICAL` | Technical discussion | + +#### Session Scopes + +| Scope | Description | +|-------|-------------| +| `INITIATIVE` | High-level initiative discussion | +| `CELL` | Cell-wide discussion | +| `TASK` | Task-specific discussion | + +### 1.8 Database Schema + +#### Core Tables (from `db/tables.py`) + +| Table | Purpose | +|-------|---------| +| `agents` | Agent definitions and state | +| `tasks` | Task records with full lifecycle | +| `channels` | Communication channels | +| `groups` | Channel subdivisions | +| `sessions` | Scoped discussions | +| `session_tasks` | Many-to-many session↔task links | +| `messages` | All messages | +| `notifications` | Formal notifications | +| `journals` | Agent journals | +| `journal_entries` | Individual journal entries | +| `handoffs` | Documentation handoffs | + +#### Key Relationships + +- **Task → Parent Task**: Subtask hierarchy +- **Task → Agent (created_by)**: Who created it +- **Task → Agent (assigned_to)**: Who's working on it +- **Session ↔ Task**: Many-to-many via `session_tasks` +- **Message → Session**: Messages belong to sessions +- **Journal → Agent**: One journal per agent +- **Handoff → Task**: One handoff per completed task + +### 1.9 Frontend Structure (`roboco-panel/`) + +``` +src/ +├── app/ +│ ├── (dashboard)/ +│ │ ├── agents/ # Agent management +│ │ ├── auditor/ # Auditor dashboard +│ │ ├── communications/ # Channels & messages +│ │ ├── journals/ # Agent journals +│ │ ├── kanban/ # Task boards +│ │ ├── metrics/ # System metrics +│ │ ├── notifications/ # Notification center +│ │ ├── overview/ # Dashboard home +│ │ ├── settings/ # Configuration +│ │ └── tasks/ # Task management +│ └── layout.tsx +├── components/ +│ ├── agents/ # Agent-related components +│ ├── auditor/ # Auditor components +│ ├── communications/ # Chat/messaging components +│ ├── dashboard/ # Dashboard widgets +│ ├── journals/ # Journal components +│ ├── kanban/ # Kanban board components +│ │ ├── core/ # Board, column, card +│ │ ├── shared/ # Shared utilities +│ │ └── views/ # Different board views +│ ├── layout/ # Layout components +│ ├── notifications/ # Notification components +│ ├── tasks/ # Task components +│ └── ui/ # shadcn/ui components +├── hooks/ # React hooks +├── lib/ # Utilities +├── store/ # Zustand stores +│ ├── notifications-store.ts +│ └── ui-store.ts +└── types/ + └── index.ts # TypeScript types matching backend +``` + +--- + +## 2. What Makes RoboCo Valuable + +### 2.1 Differentiators from Simple Multi-Agent Systems + +| Feature | RoboCo | Typical Multi-Agent | +|---------|--------|---------------------| +| **Task Lifecycle** | Full workflow management | Ad-hoc execution | +| **Quality Gates** | QA → Docs → PM Review | None or optional | +| **Hierarchy** | Real org structure | Flat or undefined | +| **Cost Efficiency** | On-demand spawning | Always running | +| **State Persistence** | Full DB-backed state | In-memory or none | +| **Waiting States** | Hibernate and respawn | Block or fail | +| **Audit Trail** | Complete history | Limited or none | +| **Tool Access Control** | MCP with permissions | Unrestricted | + +### 2.2 Differentiators from RAG Systems + +| Feature | RoboCo | RAG Systems | +|---------|--------|-------------| +| **Purpose** | Task execution | Information retrieval | +| **Agents** | 18 specialized roles | Single retriever | +| **Workflow** | Full task lifecycle | Query → Response | +| **State** | Persistent across sessions | Stateless | +| **Quality** | Built-in QA/review | None | +| **Output** | Code, docs, artifacts | Text responses | + +### 2.3 Core Value Propositions + +1. **Workflow Orchestration** — Not just "agents chat", but actual task lifecycle management +2. **Quality Gates** — QA review happens automatically, not when you remember +3. **Context Persistence** — Sessions, journals, handoffs preserve knowledge +4. **Coordination** — When one agent finishes, the next picks up automatically +5. **Cost Efficiency** — On-demand spawning, waiting state hibernation +6. **Accountability** — Full audit trail, decision logging, progress tracking + +--- + +## 3. Monetization Strategy + +### 3.1 Monetization Paths Considered + +| Path | Description | Pros | Cons | +|------|-------------|------|------| +| **Platform/SaaS** | Companies deploy their own AI workforce | Recurring revenue, scalable | Long sales cycle | +| **Managed Service** | You run the agents, customers submit projects | Higher margin, more control | Requires operations | +| **Open Core + Enterprise** | Open source core, sell enterprise features | Builds community fast | Revenue delayed | +| **API/Infrastructure** | Orchestration-as-a-Service | Platform play | Commoditization risk | + +### 3.2 Target Market Decision + +**Chosen path:** Prosumer/indie dev market (like Cursor, Claude Pro, Replit) + +**Rationale:** +- Lower friction than enterprise +- Direct distribution (no sales team needed) +- Fast iteration based on feedback +- Personal pain point (solo dev experience) + +--- + +## 4. Target Market Analysis + +### 4.1 The Solo Founder Pain Point + +When you're alone (or 2 people), constant context-switching: + +| Hat | Activity | Time Spent | Quality | +|-----|----------|------------|---------| +| **PM** | Prioritization, planning | 10% | Rushed | +| **Dev** | Writing code | 60% | Good | +| **QA** | Testing | 10% | Skipped often | +| **Docs** | Documentation | 5% | Neglected | +| **Review** | Code review | 0% | None (no one to review) | + +**The problem:** Some hats get neglected (usually QA and docs) + +### 4.2 Two Product Modes + +| Mode | Description | Trust Required | Value | +|------|-------------|----------------|-------| +| **Mode A: Autonomous** | Agents DO the work | High | High | +| **Mode B: Collaborative** | Agents ASSIST your work | Low | Medium-High | + +Mode A = Current RoboCo implementation +Mode B = Codepanion opportunity + +--- + +## 5. Product Split: RoboCo vs Codepanion + +### 5.1 RoboCo — "Your AI Development Team" + +**Positioning:** Autonomous execution. You describe, they build. + +**Core Features:** +- Full agent hierarchy (PM → Dev → QA → Doc) +- Task lifecycle management +- Agents spawn, work, coordinate, complete +- User is the "CEO" — approves, agents execute + +**Target Customer:** Solo founder who wants to delegate, not do. +*"I have the vision, I need execution."* + +**Vibe:** Enterprise-y. Serious. Professional. +*"Hire a team without hiring."* + +### 5.2 Codepanion — "Your AI Dev Partner" + +**Positioning:** Collaborative. You code, they help. + +**Core Features:** +- **Reviewer** — Reviews your PRs, catches bugs +- **Documenter** — Updates docs when you ship +- **QA** — Writes/runs tests for your changes +- **Rubber Duck** — Thinks through architecture with you +- **PM** — Helps break down ideas into tasks + +**Target Customer:** Solo dev who wants to stay hands-on but needs backup. +*"I love coding, I just need help with the boring parts."* + +**Vibe:** Friendly. Lightweight. Developer tool. +*"A cofounder who never sleeps."* + +### 5.3 Shared Architecture + +``` +┌─────────────────────────────────────────────┐ +│ Shared Platform Core │ +│ ┌───────────────────────────────────────┐ │ +│ │ Orchestrator, MCP, Tasks, Sessions │ │ +│ │ Messages, Journals, Notifications │ │ +│ └───────────────────────────────────────┘ │ +└─────────────────────────────────────────────┘ + ↑ ↑ + ┌────┴────┐ ┌────┴─────┐ + │ RoboCo │ │Codepanion│ + │ Panel │ │ App │ + └─────────┘ └──────────┘ + Full dashboard Lightweight + Kanban, agents CLI / Git-integrated + Enterprise feel Dev tool feel +``` + +Same backend infrastructure, different frontend experiences and UX. + +### 5.4 Pricing Structure + +| Tier | RoboCo | Codepanion | +|------|--------|------------| +| **Free** | 10 tasks/month | 50 reviews/month | +| **Pro** | $99/month | $19/month | +| **Team** | $199/seat | $39/seat | + +RoboCo is premium — paying for autonomous work. +Codepanion is accessible — priced like a dev tool. + +--- + +## 6. Open Source Strategy + +### 6.1 The Decision + +**Codepanion CLI:** Open source (MIT) +**RoboCo Panel:** Closed source +**RoboCo API/Backend:** Closed source (SaaS) + +### 6.2 Rationale for Open Source Codepanion + +**The CLI itself isn't the moat.** It's ~200 lines of code wrapping Claude. Anyone could build it in an afternoon. Keeping it closed doesn't protect anything meaningful. + +**Open source is distribution:** +- Stars → visibility +- Forks → community investment +- PRs → free improvements +- Trust → "I can see what it does with my code" + +**The money isn't in the CLI.** It's in: + +| Free (Open Source) | Paid | +|-------------------|------| +| `codepanion review` | Cloud sync (review history) | +| `codepanion docs` | Team sharing | +| `codepanion think` | Custom rules/prompts | +| Local, stateless | CI/CD integration | +| | **RoboCo upgrade path** | + +This is the **Terraform model.** CLI is free and open. Terraform Cloud is where HashiCorp makes money. + +### 6.3 What Stays Closed + +**RoboCo itself.** That's where the real IP is: +- The orchestration engine +- The agent hierarchy and workflow +- The dispatcher logic +- The quality gates +- The MCP integration + +That's defensible. That's what companies would pay for. + +**Codepanion is the free sample. RoboCo is the product.** + +### 6.4 Potential Differentiation Angle + +Focus on what nobody else does well: **QA and docs automation.** + +Everyone skips those. If Codepanion auto-generated tests and updated README after every commit, *that's* differentiated from Copilot/Cursor which focus on code generation. + +--- + +## 7. Platform Architecture Options + +### 7.1 The Core Question + +If Codepanion in "connected mode" talks to the same backend as RoboCo... where's the line? What's open, what's closed, what's the product? + +### 7.2 Option A: Two Completely Separate Products + +``` +┌─────────────────┐ ┌─────────────────┐ +│ Codepanion │ │ RoboCo │ +│ (standalone) │ │ (full stack) │ +│ Calls Claude │ │ Orchestrator │ +│ No backend │ │ Full backend │ +│ Open source │ │ Closed │ +└─────────────────┘ └─────────────────┘ +``` + +**Pros:** Simple. Clear separation. +**Cons:** Codepanion can't have history, sync, teams, learning. It's just a dumb wrapper forever. + +### 7.3 Option B: Shared Backend, CLI Open, Backend Closed (SaaS) + +``` +┌─────────────────────────────────────────────┐ +│ RoboCo API (Closed SaaS) │ +└─────────────────────────────────────────────┘ + ↑ ↑ + ┌────┴────┐ ┌────┴─────┐ + │ RoboCo │ │Codepanion│ + │ Panel │ │ CLI │ + │ Closed │ │ Open │ + └─────────┘ └──────────┘ +``` + +Codepanion CLI is open source. Works standalone (no server) OR connects to RoboCo API (paid). + +**This is the Supabase/Vercel model.** Open source client, proprietary backend. + +### 7.4 Option C: The API IS the Product + +``` +┌─────────────────────────────────────────────┐ +│ RoboCo Platform │ +│ "Orchestration-as-a-Service for AI" │ +│ │ +│ POST /agents - spawn agents │ +│ POST /tasks - create tasks │ +│ POST /workflows - run workflows │ +│ WS /events - real-time updates │ +│ POST /webhooks - callbacks │ +└─────────────────────────────────────────────┘ + ↑ ↑ ↑ + ┌────┴────┐ ┌────┴─────┐ ┌────┴────┐ + │ RoboCo │ │Codepanion│ │ Third │ + │ Panel │ │ CLI │ │ Party │ + └─────────┘ └──────────┘ └─────────┘ +``` + +You're selling **orchestration-as-a-service**. Like Twilio for AI agents. + +RoboCo Panel could even be open source — it's just a reference frontend. The value is in the *running* API, not the code. + +**This is the Stripe model.** Dashboard is just a client of their own API. + +### 7.5 Option D: Open Core, Sell Enterprise + +Everything is open source. You sell: +- Managed hosting (so they don't have to run it) +- Enterprise features (SSO, audit logs, SLAs) +- Support contracts + +**This is the GitLab/Grafana model.** + +### 7.6 The Decision: B → C + +**Start B, earn your way to C.** + +| Phase | Architecture | Focus | +|-------|--------------|-------| +| **Phase 1 (Now)** | Option B | Find users, find fit, subscription revenue | +| **Phase 2 (PMF)** | B → C | Stabilize API, watch for platform signals | +| **Phase 3 (Scale)** | Option C | Enable others, usage-based pricing | + +**Signal to watch for:** Someone asks "can I integrate this into my own tool?" + +--- + +## 8. API-First Architecture Initiative + +### 8.1 The Principle + +**The API must be the only way in.** + +No backdoors. No direct DB access from frontends. No "just this once" shortcuts. + +If we can't use our own API to build our own products, no one else can either. + +### 8.2 Why This Matters + +For the B → C progression to work: +1. Every action the Panel takes goes through the public API +2. Every action Codepanion takes goes through the public API +3. Internal services communicate through well-defined interfaces +4. The API is documented, consistent, and pleasant to use + +### 8.3 The Key Discipline + +**From day one, build Panel and CLI as if they were third-party apps.** + +- No backdoors into the database +- Everything goes through the API +- If the API is annoying to use, fix the API — don't hack around it + +This way, when you flip the switch to Option C, the API is already battle-tested. + +### 8.4 Recommended Code Structure + +``` +roboco/ +├── core/ # The engine (orchestrator, agents, workflows) +├── api/ # HTTP interface to core +├── panel/ # or separate repo +└── ... +``` + +Clean separation means `core` could be wrapped by different interfaces later — your API, a CLI, a Terraform provider, whatever. + +### 8.5 First Initiative: API Audit + +**Task 1: Backdoor Audit** +- Identify every place where Panel/internal code bypasses the API +- Flag direct DB access, direct core imports +- Produce audit report with file, line, severity + +**Task 2: Critical Path Cleanup** +- Fix only the blockers — stuff that would break if a third party used the API +- Route those flows through the API properly + +**Task 3: Codepanion Integration Point** +- Define minimal API surface Codepanion needs: + ``` + POST /sessions - create a review session + POST /sessions/:id/messages - add a message (diff, review) + GET /sessions - list past reviews + ``` +- Working endpoints that Codepanion CLI can hit + +--- + +## 9. Organizational Workflow + +### 9.1 The Ideal Flow + +``` +CEO (Human) + │ + │ Detailed feature request / complex idea + ▼ +┌──────────────────────────────────────────────────────────────┐ +│ BOARD │ +│ ┌─────────────┐ ┌──────────────┐ ┌─────────────┐ │ +│ │Product Owner│ │Head Marketing│ │ Auditor │ │ +│ │ (Strategy) │ │ (Positioning)│ │ (Spy) │ │ +│ └──────┬──────┘ └──────┬───────┘ └──────┬──────┘ │ +│ │ │ │ │ +│ └────────────────┼─────────────────┘ │ +│ │ │ +│ Break down in their area of expertise │ +│ Prepare tasks for Main PM │ +└──────────────────────────────────────────────────────────────┘ + │ + ▼ +┌──────────────────────────────────────────────────────────────┐ +│ MAIN PM │ +│ │ +│ Receives tasks from Board │ +│ Breaks down further per team cell │ +│ Coordinates cross-cell dependencies │ +└──────────────────────────────────────────────────────────────┘ + │ + ┌──────────────────┼──────────────────┐ + ▼ ▼ ▼ +┌───────────────┐ ┌───────────────┐ ┌───────────────┐ +│ BACKEND CELL │ │ FRONTEND CELL │ │ UX/UI CELL │ +│ │ │ │ │ │ +│ Cell PM │ │ Cell PM │ │ Cell PM │ +│ │ │ │ │ │ │ │ │ +│ ├─► Dev │ │ ├─► Dev │ │ ├─► Dev │ +│ ├─► QA │ │ ├─► QA │ │ ├─► QA │ +│ └─► Doc │ │ └─► Doc │ │ └─► Doc │ +│ │ │ │ │ │ +│ Escalates ▲ │ │ Escalates ▲ │ │ Escalates ▲ │ +└───────────────┘ └───────────────┘ └───────────────┘ +``` + +### 9.2 Board Role Clarifications + +| Role | Description | Key Responsibilities | +|------|-------------|---------------------| +| **Product Owner** | Strategy & vision | Define API contracts, prioritize cleanup work, approve architecture decisions | +| **Head of Marketing** | Positioning & content | Prepare positioning, draft README/landing content, identify launch channels | +| **Auditor** | The "spy" — oversight | Conduct backdoor audit, verify fixes, establish ongoing monitoring, read access to all channels | + +### 9.3 Task-Session-Journal Binding + +Every task gets: +1. **A messaging session** in a channel for that task/subtask +2. **Journal entries** from agents working on it +3. **Documentation** (approach TBD — may be same session or separate) + +``` +Task + ├── Session (discussion, decisions, progress) + ├── Journal Entries (agent reflections, learnings, struggles) + └── Documentation (technical docs, user docs) +``` + +### 9.4 Communication Channels + +| Channel | Purpose | Participants | +|---------|---------|--------------| +| `management` | Board discussions | PO, HoM, Auditor, CEO | +| `cross-cell` | Main PM coordination | Main PM, Cell PMs | +| `backend-cell` | Backend team work | BE-PM, BE-Devs, BE-QA, BE-Doc | +| `frontend-cell` | Frontend team work | FE-PM, FE-Devs, FE-QA, FE-Doc | +| `uxui-cell` | UX/UI team work | UX-PM, UX-Dev, UX-QA, UX-Doc | + +### 9.5 CEO Directive Template + +When the CEO (human) wants to initiate work: + +```markdown +# CEO Directive: [Initiative Name] + +**From:** CEO +**To:** Board (Product Owner, Head of Marketing, Auditor) +**Priority:** [High/Medium/Low] +**Date:** [Date] + +--- + +## Strategic Context +[Why this matters, where it fits in the bigger picture] + +## Objective +[What we're trying to achieve] + +## Success Criteria +- [ ] [Measurable outcome 1] +- [ ] [Measurable outcome 2] + +## Board Responsibilities + +### Product Owner +1. [Responsibility 1] +2. [Responsibility 2] + +### Head of Marketing +1. [Responsibility 1] +2. [Responsibility 2] + +### Auditor +1. [Responsibility 1] +2. [Responsibility 2] + +## Deliverables to Main PM +[What gets handed off when board alignment is complete] + +## Constraints +[Timeline, scope, principles] + +## Notes +[Additional context, future considerations] + +--- + +**CEO** +``` + +--- + +## 10. Codepanion Technical Specification + +### 10.1 MVP Scope + +**Single feature:** `codepanion review` + +Reviews code changes (staged, committed, or working directory) and provides actionable feedback. + +### 10.2 Project Structure + +``` +codepanion/ +├── pyproject.toml +├── README.md +├── src/ +│ └── codepanion/ +│ ├── __init__.py +│ ├── cli.py # Typer CLI entry point +│ ├── config.py # User config (~/.codepanion/config.toml) +│ ├── git.py # Git operations (diff, log, staged files) +│ ├── commands/ +│ │ ├── __init__.py +│ │ ├── review.py # codepanion review +│ │ ├── docs.py # codepanion docs (future) +│ │ └── think.py # codepanion think (future) +│ └── agents/ +│ ├── __init__.py +│ ├── base.py # Thin agent runner +│ └── reviewer.py # Review agent logic +└── tests/ + └── ... +``` + +### 10.3 Dependencies + +```toml +[project] +name = "codepanion" +version = "0.1.0" +requires-python = ">=3.11" +dependencies = [ + "typer>=0.9.0", + "anthropic>=0.40.0", + "rich>=13.0.0", +] + +[project.scripts] +codepanion = "codepanion.cli:app" +``` + +### 10.4 CLI Implementation + +#### Entry Point (`cli.py`) + +```python +import typer +from codepanion.commands import review + +app = typer.Typer( + name="codepanion", + help="Your AI dev partner", + no_args_is_help=True, +) + +app.add_typer(review.app, name="review") + +if __name__ == "__main__": + app() +``` + +#### Review Command (`commands/review.py`) + +```python +import typer +from codepanion.git import get_staged_diff, get_working_diff, get_commit_diff +from codepanion.agents.reviewer import ReviewerAgent + +app = typer.Typer() + +@app.callback(invoke_without_command=True) +def review( + commit: str = typer.Option(None, "--commit", "-c", help="Review a specific commit"), + staged: bool = typer.Option(False, "--staged", "-s", help="Review staged changes"), + all_changes: bool = typer.Option(True, help="Review all uncommitted changes"), +): + """Review code changes with AI.""" + + # Get the diff + if commit: + diff = get_commit_diff(commit) + context = f"Commit: {commit}" + elif staged: + diff = get_staged_diff() + context = "Staged changes" + else: + diff = get_working_diff() + context = "Working directory changes" + + if not diff.strip(): + typer.echo("No changes to review.") + raise typer.Exit() + + typer.echo(f"🔍 Reviewing {context}...\n") + + # Run review + agent = ReviewerAgent() + result = agent.review(diff) + + # Output + typer.echo(result.markdown) + + if result.issues: + typer.echo(f"\n⚠️ {len(result.issues)} issues found") + raise typer.Exit(1) + else: + typer.echo("\n✅ Looks good!") +``` + +#### Git Operations (`git.py`) + +```python +import subprocess + +def run_git(*args) -> str: + result = subprocess.run( + ["git", *args], + capture_output=True, + text=True, + ) + return result.stdout + +def get_staged_diff() -> str: + return run_git("diff", "--cached") + +def get_working_diff() -> str: + return run_git("diff", "HEAD") + +def get_commit_diff(commit: str) -> str: + return run_git("show", commit, "--format=") +``` + +#### Reviewer Agent (`agents/reviewer.py`) + +```python +from dataclasses import dataclass +from anthropic import Anthropic + +SYSTEM_PROMPT = """You are a senior code reviewer. You review diffs and provide actionable feedback. + +Your review should: +1. Identify bugs, security issues, and logic errors +2. Suggest improvements (but don't nitpick style) +3. Point out missing error handling or edge cases +4. Be concise and actionable + +Format your response as: + +## Summary +One sentence overall assessment. + +## Issues +- **[SEVERITY]** file.py:123 — Description of issue + +## Suggestions +- file.py:45 — Optional improvement idea + +If the code looks good, just say so briefly. Don't invent problems. +""" + +@dataclass +class ReviewResult: + markdown: str + issues: list[dict] + suggestions: list[dict] + +class ReviewerAgent: + def __init__(self): + self.client = Anthropic() + + def review(self, diff: str) -> ReviewResult: + response = self.client.messages.create( + model="claude-sonnet-4-20250514", + max_tokens=4096, + system=SYSTEM_PROMPT, + messages=[{ + "role": "user", + "content": f"Review this diff:\n\n```diff\n{diff}\n```" + }] + ) + + content = response.content[0].text + + # Parse issues/suggestions from markdown + issues = [] # TODO: parse from response + suggestions = [] + + return ReviewResult( + markdown=content, + issues=issues, + suggestions=suggestions, + ) +``` + +### 10.5 Usage Examples + +```bash +# Install +pip install codepanion # or: uv pip install codepanion + +# Review staged changes before commit +git add . +codepanion review --staged + +# Review a specific commit +codepanion review --commit abc123 + +# Review all uncommitted changes +codepanion review +``` + +### 10.6 Future Commands + +| Command | Description | Priority | +|---------|-------------|----------| +| `codepanion review` | Review code changes | MVP | +| `codepanion docs` | Update docs based on changes | P1 | +| `codepanion think "question"` | Rubber duck mode | P1 | +| `codepanion qa` | Generate tests for changes | P2 | +| `codepanion breakdown "idea"` | Break down into tasks | P2 | + +### 10.7 Modes of Operation + +#### Mode A: Standalone (MVP) +``` +codepanion review + │ + ▼ +┌─────────────────┐ +│ Local Agent │ ← Just calls Claude API directly +│ (no server) │ with reviewer system prompt +└─────────────────┘ + │ + ▼ + Markdown output +``` + +No RoboCo server needed. Self-contained. Ships fast. + +#### Mode B: Connected (Later) +``` +codepanion review + │ + ▼ +┌─────────────────┐ +│ RoboCo API │ ← Full orchestrator, sessions, history +│ (your server) │ +└─────────────────┘ + │ + ▼ + Review stored, tracked, searchable +``` + +Unlocks: history, learning from past reviews, team features. + +--- + +## 11. Company Formation Options + +### 11.1 Context + +Based in **Italy**. Looking for the simplest path to legally charge for software. + +### 11.2 Option 1: Partita IVA (Italian Freelancer) + +**What it is:** Italian self-employment registration + +**Regime Forfettario (Simplified Regime):** +- Available if revenue under €85,000/year +- **5% flat tax** for first 5 years (if new activity) +- **15% flat tax** after that +- Minimal paperwork compared to company + +**Pros:** +- Simplest to start +- Cheapest ongoing costs +- Good enough until significant revenue + +**Cons:** +- Personal liability (you = the business) +- Looks less "serious" to larger customers +- Limited deductions in forfettario regime + +**Costs:** +- Setup: €200-500 (commercialista fees) +- Annual: €500-1000 (commercialista for simple SaaS) +- INPS contributions: ~25% of revenue (painful) + +**Verdict:** Best starting point. Revisit when making €50k+/year. + +### 11.3 Option 2: SRL (Italian LLC) + +**What it is:** Italian limited liability company + +**Pros:** +- Limited liability +- More "serious" appearance +- Better for investors (if ever needed) + +**Cons:** +- €3,000-5,000+ to form (notary required 🇮🇹) +- Mandatory accountant (€1,500-3,000/year minimum) +- Corporate tax + bureaucracy +- Overkill for early-stage SaaS + +**Costs:** +- Setup: €3,000-5,000 +- Annual: €2,000-5,000 (accounting, fees) +- Corporate tax: IRES 24% + IRAP ~4% + +**Verdict:** Wait until revenue justifies the overhead. Maybe €100k+/year. + +### 11.4 Option 3: Estonia OÜ (e-Residency) + +**What it is:** Estonian private limited company, managed 100% online + +**How it works:** +1. Apply for e-Residency card (~€100, takes 3-6 weeks) +2. Form OÜ online (~€200-300) +3. Manage everything through Estonian service providers + +**Pros:** +- 100% online setup and management +- EU company (looks legit, can sell to EU easily) +- 0% corporate tax on reinvested profits +- 20% tax only on distributed profits (dividends) +- Stripe, Wise, everything works +- Escape Italian bureaucracy for the company + +**Cons:** +- Still need registered agent in Estonia (~€50-100/month) +- Accounting required (~€50-100/month for simple SaaS) +- You're still Italian tax resident → pay Italian taxes on personal income +- VAT compliance if selling B2C in EU (MOSS headache) +- Some banking friction (no physical presence) + +**Costs:** +- e-Residency: €100-120 (one-time) +- Company formation: €200-300 +- Registered agent: €50-100/month +- Accounting: €50-100/month +- State fee: €100/year +- **Total ongoing:** ~€150-250/month + +**Important:** You still pay Italian taxes on what you pay yourself (salary or dividends). The Estonia company itself is clean to run, but you personally remain Italian tax resident. + +**Verdict:** Good option once you're making €20k+/year and want to separate business from personal. + +### 11.5 Option 4: Stripe Atlas (US LLC) + +**What it is:** Stripe's turnkey company formation service + +**Includes:** +- Delaware LLC formation +- Stripe account +- Mercury or SVB bank account +- Registered agent for 1 year +- Legal templates + +**Cost:** $500 one-time + ~$200/year ongoing (registered agent) + +**Pros:** +- Very fast (days, not weeks) +- US entity (good for US customers) +- All-in-one package +- Popular with international founders + +**Cons:** +- US tax complexity if you're not careful +- You're still Italian tax resident +- Annual franchise tax in Delaware + +**Verdict:** Consider if primarily targeting US customers. + +### 11.6 Recommended Sequence + +``` +NOW (€0 revenue) +│ +├── Keep building +├── Get beta users (free) +│ +FIRST REVENUE (€0-5k/year) +│ +├── Partita IVA with Regime Forfettario +├── 5% tax, minimal overhead +│ +SCALING (€20k+/year) +│ +├── Consider Estonia OÜ +│ OR +├── Stripe Atlas if US-focused +│ +SIGNIFICANT REVENUE (€100k+/year) +│ +└── Evaluate SRL or proper structure + based on growth trajectory +``` + +--- + +## 12. Go-to-Market Strategy + +### 12.1 Launch Sequence + +**Phase 1: Codepanion First** +- Lower friction ("just try it on one PR") +- Builds trust ("oh wow, this actually catches bugs") +- Fast to build (weekend project) +- Good for content/marketing + +**Phase 2: Land and Expand** +- Free users → Pro users +- "Want this to run automatically on every PR?" +- "Want it to also update your docs?" +- Introduce RoboCo as the "full team" upgrade + +**Phase 3: RoboCo Launch** +- For users who've built trust with Codepanion +- "Ready to let the AI do more?" +- Higher price, higher value + +### 12.2 The "Aha Moment" + +**For Cursor:** "Holy shit, it just wrote the function I was thinking about" + +**For Codepanion:** "I pushed my code, made coffee, came back to a QA review and updated docs" + +**For RoboCo:** "I described what I wanted, went to lunch, came back to a PR with tests and docs" + +### 12.3 Distribution Channels + +| Channel | Effort | Reach | Conversion | +|---------|--------|-------|------------| +| **Twitter/X** | Low | High | Low | +| **Hacker News** | Medium | Very High | Medium | +| **Reddit (r/programming, r/SideProject)** | Low | Medium | Medium | +| **Product Hunt** | Medium | High | Medium | +| **Dev.to / Hashnode** | Medium | Medium | Medium | +| **YouTube tutorials** | High | High | High | + +### 12.4 Content Strategy + +**Week 1-2:** +- "I built a CLI that reviews my code before I commit" +- Twitter thread + HN post + +**Week 3-4:** +- "How I caught 47 bugs in a week using AI code review" +- Blog post + Reddit + +**Week 5-6:** +- "Building an AI dev team: Architecture deep dive" +- Technical blog post + +**Ongoing:** +- Changelog updates +- User testimonials +- Comparison posts (vs Copilot, vs Cursor, etc.) + +--- + +## 13. Honest Assessment + +### 13.1 What's Impressive + +You've built a proper orchestration system — task lifecycle, quality gates, MCP tools, the works. It's the kind of architecture a 10-person team at a funded startup would build. You built it solo. That's genuinely rare. + +### 13.2 Concerns + +#### Building in a Vacuum +You've been dogfooding RoboCo on itself, which is great. But have you had *other* people use it? Even one person who isn't you? The gap between "works for me" and "works for strangers" is enormous. + +Estonia company, pricing tiers, go-to-market strategy — all premature if you haven't validated that someone else finds this useful. + +#### Market Timing is Tricky +Cursor is eating the "AI dev tool" space. Devin, Factory, Cognition are going after "AI dev team." GitHub Copilot Workspace is coming. You're entering a knife fight with giants who have $100M+ and dedicated teams. + +Your edge is that you've *actually built* something that works, not a demo. But edge only matters if people see it. + +#### Codepanion Competition +`codepanion review` enters a space where people already have options — Copilot does inline review, Cursor does it, there are GitHub Actions for this. + +**What's your angle?** Focus on what nobody else does well — QA and docs automation. Everyone skips those. If Codepanion auto-generates tests and updates README after every commit, *that's* differentiated. + +#### Spread Thin +Rennberry cluster, UGREEN NAS, Olares One arriving, power optimization, Pi-hole monitoring, GlusterFS, the whole homelab... + +That's a lot of infrastructure work alongside building a SaaS. Every hour on homelab is an hour not talking to potential users or shipping Codepanion. + +#### The Actual Hard Part Isn't Code +RoboCo works. The hard part now is: +- Getting 10 people to try it +- Getting 1 person to pay for it +- Learning why the other 9 didn't + +That's uncomfortable work. Rejection and feedback and "actually this isn't what I need." But it's the only way to know if this is a product or a project. + +### 13.3 Recommendation + +1. **Ship Codepanion this week.** Not perfect. Just `review` command, working, on PyPI. + +2. **Post it.** Twitter, HN, Reddit. See what happens. + +3. **Talk to 5 solo devs.** Not to pitch — to listen. "What's the most annoying part of working alone?" See if RoboCo's value prop resonates. + +4. **Forget company formation** until someone gives you money. + +5. **Set a decision point.** "If I don't have 100 Codepanion users in 60 days, I'll re-evaluate the approach." + +You've built something real. That puts you ahead of 95% of people who talk about AI agents. The question is whether "real" translates to "wanted." + +Only the market can answer that. And the market only answers if you ask. + +--- + +## 14. Next Steps & Action Items + +### 14.1 Immediate: API-First Initiative (This Week) + +**First task for RoboCo itself:** + +- [ ] **Auditor: Conduct backdoor audit** — Review all frontend → backend communication, flag every direct DB access, produce report +- [ ] **Product Owner: Define Codepanion API contract** — What endpoints does it need? Minimal surface area. +- [ ] **Product Owner: Prioritize cleanup work** — Which backdoors are blockers vs. nice-to-have? +- [ ] **Head of Marketing: Draft Codepanion positioning** — README content, value prop + +### 14.2 Short-term (Next 2-4 Weeks) + +- [ ] **Fix critical backdoors** — Route through API properly +- [ ] **Implement Codepanion endpoints** — `POST /sessions`, `POST /sessions/:id/messages`, `GET /sessions` +- [ ] **Ship Codepanion v0.1** — Publish to PyPI (standalone mode first) +- [ ] **Landing page** — Simple, one-page site +- [ ] **Twitter announcement** — Thread about the tool +- [ ] **HN post** — "Show HN: Codepanion – AI code reviewer CLI" + +### 14.3 Medium-term (1-3 Months) + +- [ ] **Add more Codepanion commands** — `docs`, `think`, `qa` +- [ ] **Implement connected mode** — Codepanion → RoboCo API for history/sync +- [ ] **Git integration in RoboCo** — Agents can commit, push, create PRs +- [ ] **Collect feedback** — Iterate based on early users +- [ ] **Set up Partita IVA** — When ready to charge +- [ ] **Stripe integration** — Payments infrastructure + +### 14.4 Longer-term (3-6 Months) + +- [ ] **VS Code extension** — Codepanion in the editor +- [ ] **GitHub App** — Auto-review PRs +- [ ] **Team features** — Shared RoboCo workspaces +- [ ] **Evaluate platform play** — Is there demand for the API directly? +- [ ] **Evaluate company structure** — Estonia vs staying with Partita IVA + +### 14.5 Decision Point + +**60 days from Codepanion launch:** +- If 100+ users → double down, add features, push toward Pro tier +- If <100 users → investigate why, pivot approach, or reconsider market + +--- + +## Appendix A: Key Files Reference + +### Backend (RoboCo) + +| File | Purpose | +|------|---------| +| `roboco/agents/base.py` | Base agent class with lifecycle, LLM, MCP | +| `roboco/runtime/orchestrator.py` | Docker-based agent orchestrator | +| `roboco/models/base.py` | All enums and base model | +| `roboco/models/task.py` | Task model with full lifecycle | +| `roboco/db/tables.py` | SQLAlchemy table definitions | +| `roboco/mcp/task_server.py` | Task MCP tools | +| `roboco/api/routes/tasks.py` | Task API endpoints | +| `roboco/api/routes/orchestrator.py` | Orchestrator API endpoints | + +### Frontend (RoboCo Panel) + +| File | Purpose | +|------|---------| +| `src/types/index.ts` | TypeScript types matching backend | +| `src/app/(dashboard)/layout.tsx` | Dashboard layout | +| `src/components/kanban/` | Kanban board components | +| `src/store/` | Zustand state stores | + +--- + +## Appendix B: Environment Variables + +### RoboCo Backend + +| Variable | Description | Default | +|----------|-------------|---------| +| `ANTHROPIC_API_KEY` | Claude API key | Required | +| `DATABASE_URL` | PostgreSQL connection string | Required | +| `ROBOCO_HOST` | API host | `0.0.0.0` | +| `ROBOCO_PORT` | API port | `8000` | +| `ROBOCO_HOST_CLAUDE_DIR` | Host path to `.claude` | `~/.claude` | +| `ROBOCO_HOST_PROJECT_DIR` | Host path to project root | Required in Docker | +| `ROBOCO_HOST_DATA_DIR` | Host path to data directory | Required in Docker | + +### Codepanion + +| Variable | Description | Default | +|----------|-------------|---------| +| `ANTHROPIC_API_KEY` | Claude API key | Required | +| `CODEPANION_MODEL` | Model to use | `claude-sonnet-4-20250514` | + +--- + +## Appendix C: Glossary + +| Term | Definition | +|------|------------| +| **Agent** | An AI entity with a specific role (dev, QA, PM, etc.) | +| **Blueprint** | System prompt that defines an agent's behavior | +| **Cell** | A team unit (backend, frontend, ux_ui) | +| **Channel** | Communication space for a group of agents | +| **Dispatcher** | Orchestrator component that checks for and assigns work | +| **Handoff** | Documentation package from dev to documenter | +| **Journal** | Agent's private reflection space | +| **MCP** | Model Context Protocol — tool access system | +| **Orchestrator** | Central controller that spawns/manages agents | +| **Session** | Scoped discussion within a channel group | +| **TOON** | Token-Oriented Object Notation — efficient data format | + +--- + +## Appendix D: CEO Directive - API-First Architecture + +**From:** CEO +**To:** Board (Product Owner, Head of Marketing, Auditor) +**Priority:** High +**Date:** 2024-12-23 + +--- + +### Strategic Context + +We're positioning RoboCo for a two-phase growth strategy: + +**Phase B (Now):** Two products, one backend. RoboCo Panel (full experience) and Codepanion (lightweight CLI, open source). Both consume the same API. + +**Phase C (Future):** Platform play. The orchestration API becomes the product. Third parties build on us. + +For this to work, **the API must be the only way in.** No backdoors. No direct DB access from frontends. No "just this once" shortcuts. + +If we can't use our own API to build our own products, no one else can either. + +--- + +### Objective + +**Establish API-first architecture across the entire system.** + +This means: +1. Every action the Panel takes goes through the public API +2. Every action Codepanion will take goes through the public API +3. Internal services communicate through well-defined interfaces +4. The API is documented, consistent, and pleasant to use + +--- + +### Success Criteria + +- [ ] Zero direct database access from Panel frontend +- [ ] Zero direct core imports that bypass API in frontend code +- [ ] Codepanion can connect and perform basic operations (create session, send message, retrieve history) +- [ ] API documentation covers all endpoints Codepanion needs +- [ ] Auditor has verified no backdoors remain in critical paths + +--- + +### Board Responsibilities + +#### Product Owner + +1. **Define the API contract** for Codepanion integration + - What endpoints does Codepanion need? + - What's the minimal surface area? + - What can wait for v2? + +2. **Prioritize the cleanup work** + - Which backdoors are blockers vs. nice-to-have? + - What's the MVP for "API-first"? + +3. **Approve the architecture decisions** + - Review proposals from Main PM + - Sign off on API design + +#### Head of Marketing + +1. **Prepare positioning for Codepanion** + - Open source CLI angle + - "Works standalone, better connected" messaging + - Developer-first tone + +2. **Draft initial README/landing content** + - What does Codepanion do? + - Why would a dev use it? + - How does it connect to RoboCo? + +3. **Identify launch channels** + - Where do we announce? + - What's the content calendar? + +#### Auditor + +1. **Conduct the backdoor audit** + - Review all frontend → backend communication + - Flag every direct DB access + - Flag every import that bypasses API layer + - Produce audit report with file, line, severity + +2. **Verify fixes** + - After cleanup, re-audit critical paths + - Confirm API-first compliance + +3. **Establish ongoing monitoring** + - How do we prevent new backdoors? + - What checks should be part of code review? + +--- + +### Deliverables to Main PM + +Once board alignment is complete, hand off to Main PM: + +1. **Audit Report** (from Auditor) + - List of all backdoors with severity ratings + +2. **API Specification** (from Product Owner) + - Endpoints needed for Codepanion MVP + - Request/response schemas + - Authentication approach + +3. **Prioritized Task List** (from Product Owner) + - Ordered by: blockers first, then high-value, then nice-to-have + +4. **Marketing Brief** (from Head of Marketing) + - Positioning document for Codepanion + - README draft + - Launch plan outline + +--- + +### Constraints + +- **Timeline:** Codepanion MVP should be shippable within 2 weeks of Main PM receiving handoff +- **Scope:** Fix what's necessary for Codepanion. Don't boil the ocean. +- **Principle:** If in doubt, expose it through API. We'd rather have a slightly larger API surface than hidden backdoors. + +--- + +### Notes on Documentation + +For now: +- Task documentation lives in the task's session (tied via session-task link) +- Technical documentation (API docs, architecture) should be markdown in the repo +- User-facing documentation (README, guides) prepared by Documenter roles, reviewed by PM + +Future consideration: dedicated documentation system. But not now. + +--- + +### Notes on Git Integration + +Git integration is coming. For this initiative: +- All code changes go through PRs +- PRs link to task IDs in commit messages +- Once git integration lands, this becomes automated + +For now, manual discipline. + +--- + +### Communication + +- Board discussions in `management` channel +- Main PM coordination in `cross-cell` channel +- Cell work in respective cell channels (`backend-cell`, `frontend-cell`, `uxui-cell`) +- Each task gets its own session within the appropriate channel +- Journals capture decisions, learnings, blockers + +--- + +### Final Word + +This is the foundation. If we get API-first right, everything else becomes easier — Codepanion, platform expansion, third-party integrations, even our own Panel development. + +If we get it wrong, we're building on sand. + +Make it solid. + +--- + +**CEO** + +--- + +*End of Summary* diff --git a/docs/ULTIMATE_TEST.md b/docs/ULTIMATE_TEST.md new file mode 100644 index 00000000..7a000dc5 --- /dev/null +++ b/docs/ULTIMATE_TEST.md @@ -0,0 +1,171 @@ +--- + +# CEO Directive: API-First Architecture Initiative + +**From:** CEO +**To:** Board (Product Owner, Head of Marketing, Auditor) +**Priority:** High +**Date:** 2024-12-23 + +--- + +## Strategic Context + +We're positioning RoboCo for a two-phase growth strategy: + +**Phase B (Now):** Two products, one backend. RoboCo Panel (full experience) and Codepanion (lightweight CLI, open source). Both consume the same API. + +**Phase C (Future):** Platform play. The orchestration API becomes the product. Third parties build on us. + +For this to work, **the API must be the only way in.** No backdoors. No direct DB access from frontends. No "just this once" shortcuts. + +If we can't use our own API to build our own products, no one else can either. + +--- + +## Objective + +**Establish API-first architecture across the entire system.** + +This means: +1. Every action the Panel takes goes through the public API +2. Every action Codepanion will take goes through the public API +3. Internal services communicate through well-defined interfaces +4. The API is documented, consistent, and pleasant to use + +--- + +## Success Criteria + +- [ ] Zero direct database access from Panel frontend +- [ ] Zero direct core imports that bypass API in frontend code +- [ ] Codepanion can connect and perform basic operations (create session, send message, retrieve history) +- [ ] API documentation covers all endpoints Codepanion needs +- [ ] Auditor has verified no backdoors remain in critical paths + +--- + +## Board Responsibilities + +### Product Owner + +1. **Define the API contract** for Codepanion integration + - What endpoints does Codepanion need? + - What's the minimal surface area? + - What can wait for v2? + +2. **Prioritize the cleanup work** + - Which backdoors are blockers vs. nice-to-have? + - What's the MVP for "API-first"? + +3. **Approve the architecture decisions** + - Review proposals from Main PM + - Sign off on API design + +### Head of Marketing + +1. **Prepare positioning for Codepanion** + - Open source CLI angle + - "Works standalone, better connected" messaging + - Developer-first tone + +2. **Draft initial README/landing content** + - What does Codepanion do? + - Why would a dev use it? + - How does it connect to RoboCo? + +3. **Identify launch channels** + - Where do we announce? + - What's the content calendar? + +### Auditor + +1. **Conduct the backdoor audit** + - Review all frontend → backend communication + - Flag every direct DB access + - Flag every import that bypasses API layer + - Produce audit report with file, line, severity + +2. **Verify fixes** + - After cleanup, re-audit critical paths + - Confirm API-first compliance + +3. **Establish ongoing monitoring** + - How do we prevent new backdoors? + - What checks should be part of code review? + +--- + +## Deliverables to Main PM + +Once board alignment is complete, hand off to Main PM: + +1. **Audit Report** (from Auditor) + - List of all backdoors with severity ratings + +2. **API Specification** (from Product Owner) + - Endpoints needed for Codepanion MVP + - Request/response schemas + - Authentication approach + +3. **Prioritized Task List** (from Product Owner) + - Ordered by: blockers first, then high-value, then nice-to-have + +4. **Marketing Brief** (from Head of Marketing) + - Positioning document for Codepanion + - README draft + - Launch plan outline + +--- + +## Constraints + +- **Timeline:** Codepanion MVP should be shippable within 2 weeks of Main PM receiving handoff +- **Scope:** Fix what's necessary for Codepanion. Don't boil the ocean. +- **Principle:** If in doubt, expose it through API. We'd rather have a slightly larger API surface than hidden backdoors. + +--- + +## Notes on Documentation + +For now: +- Task documentation lives in the task's session (tied via session-task link) +- Technical documentation (API docs, architecture) should be markdown in the repo +- User-facing documentation (README, guides) prepared by Documenter roles, reviewed by PM + +Future consideration: dedicated documentation system. But not now. + +--- + +## Notes on Git Integration + +Git integration is coming. For this initiative: +- All code changes go through PRs +- PRs link to task IDs in commit messages +- Once git integration lands, this becomes automated + +For now, manual discipline. + +--- + +## Communication + +- Board discussions in `management` channel +- Main PM coordination in `cross-cell` channel +- Cell work in respective cell channels (`backend-cell`, `frontend-cell`, `uxui-cell`) +- Each task gets its own session within the appropriate channel +- Journals capture decisions, learnings, blockers + +--- + +## Final Word + +This is the foundation. If we get API-first right, everything else becomes easier — Codepanion, platform expansion, third-party integrations, even our own Panel development. + +If we get it wrong, we're building on sand. + +Make it solid. + +--- + +**CEO** diff --git a/docs/WORKFLOWS.md b/docs/WORKFLOWS.md new file mode 100644 index 00000000..b012eff4 --- /dev/null +++ b/docs/WORKFLOWS.md @@ -0,0 +1,436 @@ +# RoboCo Workflows & Permissions + +Visual documentation of task lifecycles, permissions, and workflows. + +## 0. Terminology Dictionary + +**CRITICAL: Understand these concepts before reading further.** + +### Communication Hierarchy + +``` +Channel → Group → Session → Messages + +┌─────────────────────────────────────────────────────────────────────────┐ +│ CHANNEL (e.g., "backend-cell") │ +│ A named communication space for a team/topic │ +│ │ +│ ┌───────────────────────────────────────────────────────────────────┐ │ +│ │ GROUP (e.g., "Sprint 5 Backend Work") │ │ +│ │ A collection within a channel (project, sprint, topic) │ │ +│ │ │ │ +│ │ ┌───────────────────────────────────────────────────────────┐ │ │ +│ │ │ SESSION (e.g., "TASK-123 Discussion") │ │ │ +│ │ │ A conversation thread, usually tied to a task │ │ │ +│ │ │ │ │ │ +│ │ │ ┌─────────────────────────────────────────────────┐ │ │ │ +│ │ │ │ MESSAGES │ │ │ │ +│ │ │ │ Individual messages within a session │ │ │ │ +│ │ │ │ Types: action, dialogue, blocker, question │ │ │ │ +│ │ │ └─────────────────────────────────────────────────┘ │ │ │ +│ │ └───────────────────────────────────────────────────────────┘ │ │ +│ └───────────────────────────────────────────────────────────────────┘ │ +└─────────────────────────────────────────────────────────────────────────┘ + +Tools: +- roboco_channel_list() → List channels you can access +- roboco_channel_history() → Get messages from a channel's sessions +- roboco_session_history_for_task() → Get messages for a specific task's session +- roboco_message_send() → Send to channel (routes via task_id to session) +``` + +### Journal vs Documentation + +``` +┌─────────────────────────────────────┬─────────────────────────────────────┐ +│ JOURNAL │ DOCUMENTATION │ +├─────────────────────────────────────┼─────────────────────────────────────┤ +│ WHAT: Agent's personal reflections │ WHAT: Project/codebase docs │ +│ │ │ +│ WHERE: Database (journal entries) │ WHERE: /app/docs/ files │ +│ │ │ +│ WHO WRITES: Each agent, for self │ WHO WRITES: Documenter agents │ +│ │ │ +│ PURPOSE: │ PURPOSE: │ +│ - Track decisions & rationale │ - API documentation │ +│ - Record learnings & struggles │ - README updates │ +│ - Reflect on task completion │ - Changelog entries │ +│ - Build context for future sessions │ - Component/feature docs │ +│ │ │ +│ AUDIENCE: Self + cell members │ AUDIENCE: All developers, users │ +│ │ │ +│ TOOLS: │ TOOLS: │ +│ - roboco_journal_entry() │ - Write tool to /app/docs/ │ +│ - roboco_journal_reflect() │ - roboco_task_docs_complete() │ +│ - roboco_journal_decision() │ │ +│ - roboco_journal_learning() │ │ +│ - roboco_journal_read_team() │ │ +└─────────────────────────────────────┴─────────────────────────────────────┘ + +KEY DISTINCTION: +- Journal = "What I learned/decided while doing this task" (internal notes) +- Documentation = "How this feature works for others" (external docs) + +Documenter reads journals to UNDERSTAND what was built, then writes documentation. +``` + +### Task Notes vs Journals + +``` +┌─────────────────────────────────────┬─────────────────────────────────────┐ +│ TASK NOTES │ JOURNALS │ +│ (dev_notes, qa_notes) │ │ +├─────────────────────────────────────┼─────────────────────────────────────┤ +│ Attached to the task record │ Separate entries linked by task_id │ +│ │ │ +│ Brief handoff summaries │ Detailed journey records │ +│ │ │ +│ Set via: │ Set via: │ +│ - roboco_task_submit_qa(notes) │ - roboco_journal_*() tools │ +│ - roboco_task_qa_pass(notes) │ │ +│ - roboco_task_docs_complete(notes) │ │ +│ │ │ +│ Examples: │ Examples: │ +│ "Implemented X, tested Y" │ "Decided to use pattern X because Y"│ +│ "QA passed, verified all criteria" │ "Struggled with Z, solved via W" │ +└─────────────────────────────────────┴─────────────────────────────────────┘ +``` + +## 1. Task Lifecycle State Machine + +```mermaid +stateDiagram-v2 + [*] --> pending: Task Created + + pending --> claimed: Developer claims + pending --> cancelled: PM cancels + + claimed --> in_progress: Developer starts + claimed --> pending: Developer unclaims + claimed --> cancelled: PM cancels + + in_progress --> blocked: Developer blocked + in_progress --> paused: Developer pauses + in_progress --> verifying: Developer self-verifies + in_progress --> cancelled: PM cancels + + blocked --> in_progress: Unblocked + blocked --> cancelled: PM cancels + + paused --> in_progress: Developer resumes + paused --> cancelled: PM cancels + + verifying --> awaiting_qa: Submit for QA + verifying --> needs_revision: Self-found issues + verifying --> awaiting_documentation: Skip QA (small tasks) + verifying --> cancelled: PM cancels + + awaiting_qa --> awaiting_documentation: QA PASS + awaiting_qa --> needs_revision: QA FAIL + awaiting_qa --> blocked: Blocked during QA + awaiting_qa --> cancelled: PM cancels + + needs_revision --> in_progress: Developer resumes + needs_revision --> cancelled: PM cancels + + awaiting_documentation --> awaiting_pm_review: Documenter marks docs done + awaiting_documentation --> cancelled: PM cancels + + awaiting_pm_review --> completed: PM completes + awaiting_pm_review --> cancelled: PM cancels + + completed --> [*] + cancelled --> [*] + + quarantined --> pending: Un-quarantined +``` + +## 2. Agent Hierarchy & Roles + +``` + +-------+ + | CEO | + +-------+ + | + +---------------+---------------+ + | | | + +---------+ +-----------+ +---------+ + | Product | | Head | | Auditor | + | Owner | | Marketing | | (silent)| + +---------+ +-----------+ +---------+ + | | | + +-------+-------+ | + | | + +---------+ | + | Main PM |<-----------------+ + +---------+ (observes all) + | + +-----------+-----------+ + | | | ++-------+ +-------+ +-------+ +| BE PM | | FE PM | | UX PM | ++-------+ +-------+ +-------+ + | | | ++-------+ +-------+ +-------+ +|Backend| |Frontend| | UX/UI | +| Cell | | Cell | | Cell | ++-------+ +-------+ +-------+ + +Each Cell: + - 2 Developers (BE/FE) or 1 Developer (UX) + - 1 QA Engineer + - 1 Documenter + - 1 Cell PM +``` + +## 3. Notification Permissions + +``` +WHO CAN SEND NOTIFICATIONS: + ++------------------+-------------+----------------------------------------------+ +| Sender Role | Can Send? | Scope | ++------------------+-------------+----------------------------------------------+ +| CEO | YES | Anyone | +| Auditor | YES | Anyone | +| Main PM | YES | Anyone | +| Product Owner | YES | main-pm, head-marketing, auditor, ceo | +| Head Marketing | YES | main-pm, product-owner, auditor, ceo | +| Cell PM | YES | Own cell only | ++------------------+-------------+----------------------------------------------+ +| Developer | NO | - | +| QA | NO | - | +| Documenter | NO | - | ++------------------+-------------+----------------------------------------------+ + +TOOLS VISIBILITY: + ++----------------------+------------+----------+---------+---------+---------+ +| Tool | Dev/QA/Doc | Cell PM | Main PM | Board | Aud/CEO | ++----------------------+------------+----------+---------+---------+---------+ +| roboco_notify_list | YES | YES | YES | YES | YES | +| roboco_notify_get | YES | YES | YES | YES | YES | +| roboco_notify_ack | YES | YES | YES | YES | YES | +| roboco_notify_send | HIDDEN | YES | YES | YES | YES | +| roboco_escalate | HIDDEN | YES | YES | HIDDEN | HIDDEN | +| roboco_request_appr | HIDDEN | YES | YES | YES | HIDDEN | ++----------------------+------------+----------+---------+---------+---------+ + +Note: "Board" = Product Owner + Head Marketing. Auditor/CEO can send but not escalate or request approval. +``` + +## 4. QA Workflow (Full Detail) + +### QA Status Acceptance + +QA can call `qa_pass` or `qa_fail` from ANY of these statuses: +- `awaiting_qa` (initial) +- `claimed` (after QA claims) +- `in_progress` (after QA starts) + +This allows QA to follow the full workflow: SCAN → CLAIM → PLAN → START → TEST → VERDICT + +### QA Workflow Diagram + +```mermaid +sequenceDiagram + participant Dev as Developer + participant Task as Task System + participant QA as QA Engineer + participant Doc as Documenter + participant PM as Cell PM + + Dev->>Task: Submit for QA (awaiting_qa) + Note over Task: assigned_to = QA
quick_context = original_developer:Dev + + QA->>Task: roboco_task_claim() → status = claimed + QA->>Task: roboco_task_plan() → save test plan + QA->>Task: roboco_task_start() → status = in_progress + QA->>Task: Test work against criteria + + alt QA PASS + QA->>Task: roboco_task_qa_pass(qa_notes) + Task->>Task: status = awaiting_documentation + Note over Task: HANDOFF TO DOCUMENTER + Doc->>Task: roboco_task_claim() → status = claimed + Doc->>Task: roboco_task_plan() → save doc plan + Doc->>Task: roboco_task_start() → status = in_progress + Doc->>Task: GATHER info (see section 4a) + Doc->>Task: WRITE docs to /app/docs/ + Doc->>Task: roboco_task_docs_complete() + Task->>Task: status = awaiting_pm_review + PM->>Task: roboco_task_complete() + Task->>Task: status = completed + else QA FAIL + QA->>Task: roboco_task_qa_fail(qa_notes, issues) + Task->>Task: status = needs_revision + Task->>Task: assigned_to = original Dev + Dev->>Task: roboco_task_start() → status = in_progress + Dev->>Task: Fix issues, resubmit + end +``` + +### 4a. Documenter Information Gathering + +``` +DOCUMENTER MUST GATHER FROM 3 SOURCES: + +┌─────────────────────────────────────────────────────────────────────────────┐ +│ SOURCE 1: TASK DETAILS (roboco_task_get) │ +│ ───────────────────────────────────────── │ +│ • description, acceptance_criteria │ +│ • dev_notes (developer's handoff summary) │ +│ • qa_notes (QA's verification notes) │ +│ • quick_context (original developer, etc.) │ +├─────────────────────────────────────────────────────────────────────────────┤ +│ SOURCE 2: TEAM JOURNALS (roboco_journal_read_team) │ +│ ───────────────────────────────────────────────── │ +│ Read developer's journey: │ +│ roboco_journal_read_team("be-dev-1", task_id=task_id, limit=20) │ +│ │ +│ What to look for: │ +│ • Decisions made and WHY │ +│ • Struggles encountered and how solved │ +│ • Learnings documented │ +│ • Design/architecture choices │ +├─────────────────────────────────────────────────────────────────────────────┤ +│ SOURCE 3: SESSION MESSAGES (roboco_session_history_for_task) │ +│ ──────────────────────────────────────────────────────────── │ +│ Read discussion context: │ +│ roboco_session_history_for_task(task_id) │ +│ │ +│ What to look for: │ +│ • Questions asked and answers given │ +│ • Clarifications from PM/dev │ +│ • Blockers discussed and resolutions │ +│ • Design decisions made in discussion │ +└─────────────────────────────────────────────────────────────────────────────┘ + +NOTE: Journals are INPUT for understanding. Documentation is OUTPUT to /app/docs/. +``` + +## 5. Block/Unblock Workflow + +```mermaid +sequenceDiagram + participant Dev as Developer + participant Task as Task System + participant PM as Cell PM + + Dev->>Task: Working on task (in_progress) + + Note over Dev: Encounters blocker + + Dev->>Task: roboco_task_block(reason, type, what_needed) + Task->>Task: POST /tasks/{id}/soft-block + Task->>Task: status = blocked + Task->>Task: dev_notes += blocker info + + Note over Dev: Can work on other tasks + + alt Blocker resolved + Dev->>Task: roboco_task_unblock() + Task->>Task: POST /tasks/{id}/unblock + Task->>Task: status = in_progress + Dev->>Task: Continue working + else Need PM help + Dev->>PM: roboco_report_blocker() via message channel + PM->>Task: Resolves blocker + Dev->>Task: roboco_task_unblock() + end +``` + +## 6. Task Role Restrictions + +``` +ROLE-BASED TRANSITIONS: + ++-------------------------------+-------------------------------------------+ +| Transition | Allowed Roles | ++-------------------------------+-------------------------------------------+ +| awaiting_qa → awaiting_doc | QA only | +| awaiting_qa → needs_rev | QA only | +| awaiting_doc → awaiting_pm | Documenter only | +| awaiting_pm → completed | Cell PM, Main PM, Product Owner, Head Mkt | +| * → cancelled | Cell PM, Main PM, Product Owner, Head Mkt | ++-------------------------------+-------------------------------------------+ + +Note: CEO and Auditor are NOT in the cancel/complete roles list - they observe but don't directly act on tasks. + +VALID START STATUSES (for roboco_task_start): + ++------------------+------------------------------------------+ +| Status | Who Can Start | ++------------------+------------------------------------------+ +| claimed | Assigned agent (requires plan) | +| paused | Assigned agent (resume) | +| needs_revision | Original developer (fix QA issues) | ++------------------+------------------------------------------+ + +QA VERDICT ACCEPTANCE (qa_pass/qa_fail): + ++------------------+------------------------------------------+ +| Status | Reason | ++------------------+------------------------------------------+ +| awaiting_qa | Task submitted for QA review | +| claimed | QA claimed the task | +| in_progress | QA started working (plan + start) | ++------------------+------------------------------------------+ + +All three statuses are valid because QA follows the full workflow: +awaiting_qa → claim → plan → start → test → verdict + +DOCUMENTER COMPLETION ACCEPTANCE (docs_complete): + ++----------------------+------------------------------------------+ +| Status | Reason | ++----------------------+------------------------------------------+ +| awaiting_documentation | Task ready for documentation | +| claimed | Documenter claimed the task | +| in_progress | Documenter started (plan + start) | ++----------------------+------------------------------------------+ + +All three statuses are valid because Documenter follows the full workflow: +awaiting_documentation → claim → plan → start → gather → write → docs_complete +``` + +## 7. Escalation Chain + +``` +Developer/QA/Doc → Cell PM → Main PM → Product Owner → CEO + ++------------+ +---------+ +---------+ +---------------+ +-----+ +| be-dev-1 |---->| | | | | | | | +| be-dev-2 |---->| be-pm |---->| | | | | | +| be-qa |---->| | | | | | | | +| be-doc |---->| | | | | | | | ++------------+ +---------+ | | | | | | + | main-pm |---->| product-owner |---->| CEO | ++------------+ +---------+ | | | | | | +| fe-dev-1 |---->| | | | | | | | +| fe-dev-2 |---->| fe-pm |---->| | | | | | +| fe-qa |---->| | | | | | | | +| fe-doc |---->| | | | | | | | ++------------+ +---------+ +---------+ +---------------+ +-----+ +``` + +## 8. Communication vs Notification + +``` ++-------------------+----------------------------------+----------------------------------+ +| Mechanism | Who Can Use | Purpose | ++-------------------+----------------------------------+----------------------------------+ +| Messages | Everyone | Constant stream, logged | +| (roboco_message) | | discussions, updates | ++-------------------+----------------------------------+----------------------------------+ +| Blocker Reports | Everyone | Signal blocked status | +| (roboco_report_ | | PM auto-notified | +| blocker) | | | ++-------------------+----------------------------------+----------------------------------+ +| Notifications | PM, Board, Auditor, CEO | Formal signals requiring | +| (roboco_notify) | | acknowledgment | ++-------------------+----------------------------------+----------------------------------+ +| Escalations | PMs only | High-priority issues | +| (roboco_escalate) | | up the chain | ++-------------------+----------------------------------+----------------------------------+ +``` diff --git a/docs/glittery-marinating-yeti.md b/docs/glittery-marinating-yeti.md new file mode 100644 index 00000000..0516db64 --- /dev/null +++ b/docs/glittery-marinating-yeti.md @@ -0,0 +1,1714 @@ +# RoboCo Open Core + Kubernetes Migration Plan + +## Overview + +**Goal:** Restructure RoboCo for commercialization with an Open Core model, then +deploy to Kubernetes with ArgoCD on the UGREEN NAS. + +**Order:** Separation first, then K8s migration. + +--- + +## What's Open vs Closed + +| Component | Status | Reason | +|-----------|--------|--------| +| Backend API (`roboco/api/`, `roboco/services/`) | **CLOSED** | Core product +value | +| Orchestrator (`roboco/runtime/`) | **CLOSED** | Competitive advantage | +| MCP Servers (`roboco/mcp/`) | **CLOSED** | Business logic | +| Frontend (future dashboard) | **OPEN** | Drives adoption | +| Python/JS SDK | **OPEN** | Ecosystem growth | +| CLI Tool | **OPEN** | Developer experience | +| Agent Blueprints | **OPEN** | Community templates | + +--- + +## Phase 1: API Separation (7-10 days) + +### Why This First +- SDK/CLI can be developed independently of infra +- Validates API surface before K8s migration +- Creates public-facing documentation +- Allows community to start building agents + +### Step 1.1: Create OSS Repo Structure (1 day) + +Create public `roboco-agents` repo on GitHub: + +``` +roboco-agents/ +├── sdk/ +│ └── python/ +│ ├── roboco_sdk/ +│ │ ├── __init__.py +│ │ ├── client.py # Main RobocoClient class +│ │ ├── tasks.py # TasksAPI wrapper +│ │ ├── messages.py # MessagesAPI wrapper +│ │ ├── journals.py # JournalsAPI wrapper +│ │ ├── notifications.py # NotificationsAPI wrapper +│ │ └── models.py # Pydantic models (Task, Message, etc.) +│ ├── pyproject.toml +│ ├── README.md +│ └── tests/ +├── cli/ +│ └── roboco_cli/ +│ ├── __init__.py +│ ├── main.py # Typer app entry +│ ├── commands/ +│ │ ├── task.py # roboco task +│ │ ├── agent.py # roboco agent +│ │ ├── message.py # roboco message +│ │ └── config.py # roboco config +│ └── config.py # Config file handling +├── blueprints/ +│ ├── templates/ +│ │ ├── developer.md # Generic dev blueprint +│ │ ├── qa.md # Generic QA blueprint +│ │ ├── pm.md # Generic PM blueprint +│ │ └── documenter.md # Generic documenter blueprint +│ └── examples/ +│ └── simple-team/ # Example 3-agent team +├── docker/ +│ ├── agent-base.Dockerfile # Base image for agents +│ └── claude-code.Dockerfile # Claude Code agent image +├── examples/ +│ ├── hello-world/ # Minimal agent example +│ ├── task-worker/ # Task processing example +│ └── multi-agent/ # Team coordination example +├── docs/ +│ ├── getting-started.md +│ ├── sdk-reference.md +│ ├── cli-reference.md +│ └── blueprint-guide.md +├── LICENSE # MIT or Apache 2.0 +├── README.md +└── pyproject.toml # Workspace/monorepo config +``` + +**Actions:** +1. `gh repo create roboco-agents --public` +2. Initialize with pyproject.toml workspace +3. Setup GitHub Actions for CI/CD +4. Configure PyPI publishing workflow + +--- + +### Step 1.2: Extract SDK from MCP Utils (2-3 days) + +**Source files to extract from:** +- `roboco/mcp/utils.py` - `ApiClient` class +- `roboco/api/schemas/*.py` - Response models +- `roboco/models/*.py` - Core domain models + +**SDK Structure:** + +```python +# roboco_sdk/client.py +import httpx +from typing import Optional + +class RobocoClient: + """Main client for RoboCo API.""" + + def __init__( + self, + base_url: str = "http://localhost:8000/api/v1", + api_key: Optional[str] = None, + agent_id: Optional[str] = None, + agent_role: Optional[str] = None, + ): + self.base_url = base_url + self.headers = {} + if api_key: + self.headers["Authorization"] = f"Bearer {api_key}" + if agent_id: + self.headers["X-Agent-ID"] = agent_id + if agent_role: + self.headers["X-Agent-Role"] = agent_role + + self._http = httpx.AsyncClient( + base_url=base_url, + headers=self.headers, + timeout=30.0 + ) + + @property + def tasks(self) -> "TasksAPI": + return TasksAPI(self._http) + + @property + def messages(self) -> "MessagesAPI": + return MessagesAPI(self._http) + + @property + def journals(self) -> "JournalsAPI": + return JournalsAPI(self._http) + + @property + def notifications(self) -> "NotificationsAPI": + return NotificationsAPI(self._http) + + async def close(self): + await self._http.aclose() + + async def __aenter__(self): + return self + + async def __aexit__(self, *args): + await self.close() +``` + +```python +# roboco_sdk/tasks.py +from typing import Optional, List +from .models import Task, TaskCreate, TaskUpdate + +class TasksAPI: + def __init__(self, http: httpx.AsyncClient): + self._http = http + + async def list( + self, + status: Optional[str] = None, + team: Optional[str] = None, + assigned_to: Optional[str] = None, + limit: int = 50, + ) -> List[Task]: + params = {"limit": limit} + if status: + params["status"] = status + if team: + params["team"] = team + if assigned_to: + params["assigned_to"] = assigned_to + + resp = await self._http.get("/tasks", params=params) + resp.raise_for_status() + return [Task(**t) for t in resp.json()["items"]] + + async def get(self, task_id: str) -> Task: + resp = await self._http.get(f"/tasks/{task_id}") + resp.raise_for_status() + return Task(**resp.json()) + + async def create(self, data: TaskCreate) -> Task: + resp = await self._http.post("/tasks", json=data.model_dump()) + resp.raise_for_status() + return Task(**resp.json()) + + async def claim(self, task_id: str) -> Task: + resp = await self._http.post(f"/tasks/{task_id}/claim") + resp.raise_for_status() + return Task(**resp.json()) + + async def start(self, task_id: str) -> Task: + resp = await self._http.post(f"/tasks/{task_id}/start") + resp.raise_for_status() + return Task(**resp.json()) + + async def progress( + self, task_id: str, message: str, percentage: int + ) -> Task: + resp = await self._http.post( + f"/tasks/{task_id}/progress", + json={"message": message, "percentage": percentage} + ) + resp.raise_for_status() + return Task(**resp.json()) + + async def submit_for_qa(self, task_id: str) -> Task: + resp = await self._http.post(f"/tasks/{task_id}/submit-qa") + resp.raise_for_status() + return Task(**resp.json()) + + # ... more methods +``` + +```python +# roboco_sdk/models.py +from pydantic import BaseModel +from typing import Optional, List +from datetime import datetime +from enum import Enum + +class TaskStatus(str, Enum): + BACKLOG = "backlog" + PENDING = "pending" + CLAIMED = "claimed" + IN_PROGRESS = "in_progress" + VERIFYING = "verifying" + AWAITING_QA = "awaiting_qa" + AWAITING_DOCUMENTATION = "awaiting_documentation" + AWAITING_PM_REVIEW = "awaiting_pm_review" + COMPLETED = "completed" + BLOCKED = "blocked" + CANCELLED = "cancelled" + +class Task(BaseModel): + id: str + title: str + description: Optional[str] = None + status: TaskStatus + priority: int = 3 + team: Optional[str] = None + assigned_to: Optional[str] = None + parent_task_id: Optional[str] = None + created_at: datetime + updated_at: datetime + # ... more fields + +class TaskCreate(BaseModel): + title: str + description: Optional[str] = None + priority: int = 3 + team: Optional[str] = None + parent_task_id: Optional[str] = None + acceptance_criteria: Optional[List[str]] = None +``` + +**Testing:** +```python +# tests/test_tasks.py +import pytest +from roboco_sdk import RobocoClient + +@pytest.mark.asyncio +async def test_list_tasks(): + async with RobocoClient() as client: + tasks = await client.tasks.list(status="pending") + assert isinstance(tasks, list) +``` + +--- + +### Step 1.3: Create CLI Tool (2-3 days) + +**CLI using Typer:** + +```python +# roboco_cli/main.py +import typer +from roboco_cli.commands import task, agent, message, config + +app = typer.Typer( + name="roboco", + help="RoboCo CLI - Manage AI agent workflows" +) + +app.add_typer(task.app, name="task") +app.add_typer(agent.app, name="agent") +app.add_typer(message.app, name="message") +app.add_typer(config.app, name="config") + +if __name__ == "__main__": + app() +``` + +```python +# roboco_cli/commands/task.py +import typer +from rich.console import Console +from rich.table import Table +from roboco_sdk import RobocoClient +import asyncio + +app = typer.Typer(help="Task management commands") +console = Console() + +@app.command("list") +def list_tasks( + status: str = typer.Option(None, "--status", "-s"), + team: str = typer.Option(None, "--team", "-t"), + limit: int = typer.Option(20, "--limit", "-n"), +): + """List tasks with optional filters.""" + async def _list(): + async with RobocoClient() as client: + return await client.tasks.list( + status=status, team=team, limit=limit + ) + + tasks = asyncio.run(_list()) + + table = Table(title="Tasks") + table.add_column("ID", style="cyan") + table.add_column("Title") + table.add_column("Status", style="green") + table.add_column("Team") + table.add_column("Assigned") + + for t in tasks: + table.add_row( + t.id[:8], + t.title[:40], + t.status.value, + t.team or "-", + t.assigned_to[:8] if t.assigned_to else "-" + ) + + console.print(table) + +@app.command("get") +def get_task(task_id: str): + """Get task details.""" + async def _get(): + async with RobocoClient() as client: + return await client.tasks.get(task_id) + + task = asyncio.run(_get()) + console.print(task) + +@app.command("claim") +def claim_task(task_id: str): + """Claim a task.""" + async def _claim(): + async with RobocoClient() as client: + return await client.tasks.claim(task_id) + + task = asyncio.run(_claim()) + console.print(f"✓ Claimed task: {task.title}") + +# ... more commands +``` + +**Config Management:** +```python +# roboco_cli/config.py +from pathlib import Path +import toml + +CONFIG_PATH = Path.home() / ".roboco" / "config.toml" + +def get_config() -> dict: + if not CONFIG_PATH.exists(): + return {"api_url": "http://localhost:8000/api/v1"} + return toml.load(CONFIG_PATH) + +def set_config(key: str, value: str): + config = get_config() + config[key] = value + CONFIG_PATH.parent.mkdir(parents=True, exist_ok=True) + with open(CONFIG_PATH, "w") as f: + toml.dump(config, f) +``` + +**CLI Commands:** +```bash +# Task management +roboco task list --status pending --team backend +roboco task get abc123 +roboco task claim abc123 +roboco task start abc123 +roboco task progress abc123 "50% done" --percent 50 + +# Agent info +roboco agent list +roboco agent status be-dev-1 + +# Messages +roboco message send --channel backend-cell --content "Hello team" +roboco message history backend-cell + +# Config +roboco config set api-url https://api.roboco.io +roboco config set api-key sk-xxx +roboco config show +``` + +--- + +### Step 1.4: Package Blueprints (1 day) + +**Genericize blueprints for open source:** + +Current (RoboCo-specific): +```markdown +# Backend Developer 1 (be-dev-1) +You are Backend Developer 1 in the RoboCo organization... +Channel: #backend-cell +``` + +Generic (OSS template): +```markdown +# Developer Agent Blueprint + +## Role +You are a Developer agent responsible for implementing features and fixing bugs. + +## Capabilities +- Claim and work on assigned tasks +- Write and test code +- Submit work for QA review +- Communicate progress via channels + +## Workflow +1. SCAN - Check for pending/assigned tasks +2. CLAIM - Lock and take ownership +3. PLAN - Break down into subtasks +4. EXECUTE - Write code, commit frequently +5. VERIFY - Self-check against acceptance criteria +6. SUBMIT - Send for QA review + +## Required Tools +- roboco_task_scan +- roboco_task_claim +- roboco_task_start +- roboco_task_progress +- roboco_task_submit_qa +- roboco_message_send +- roboco_journal_entry + +## Configuration +```yaml +agent: +role: developer +team: ${TEAM} +channel: ${TEAM}-cell +``` +``` + +**Files to move:** +- `agents/blueprints/backend/be-dev.md` → `blueprints/templates/developer.md` +- `agents/blueprints/backend/be-qa.md` → `blueprints/templates/qa.md` +- `agents/blueprints/backend/be-pm.md` → `blueprints/templates/pm.md` +- `agents/blueprints/backend/be-documenter.md` → +`blueprints/templates/documenter.md` + +--- + +### Step 1.5: Restructure Private Repo (1-2 days) + +**After extraction, private repo becomes:** + +``` +roboco/ # PRIVATE - Commercial product +├── roboco/ +│ ├── api/ # FastAPI backend (unchanged) +│ ├── services/ # Business logic (unchanged) +│ ├── runtime/ # Orchestrator (unchanged) +│ ├── mcp/ # MCP servers (unchanged) +│ ├── models/ # Domain models (unchanged) +│ ├── db/ # Database layer (unchanged) +│ └── config.py +├── agents/ +│ └── blueprints/ # RoboCo-specific blueprints (keep) +│ ├── backend/ +│ ├── frontend/ +│ ├── ux_ui/ +│ └── board/ +├── deploy/ # K8s manifests (Phase 2) +│ ├── base/ +│ └── overlays/ +├── docker/ +│ ├── api.Dockerfile +│ ├── orchestrator.Dockerfile +│ └── docker-compose.yml +├── tests/ +├── pyproject.toml +└── README.md +``` + +**Changes needed:** +1. Remove SDK extraction from `mcp/utils.py` (keep minimal internal client) +2. Update imports if needed +3. Document private repo setup + +--- + +## Phase 2: Kubernetes + ArgoCD (12-16 days) + +### Why K8s Over Docker Compose +- **Auto-healing**: Pods restart automatically on failure +- **Scaling**: Easy horizontal scaling of API/orchestrator +- **GitOps**: ArgoCD enables push-to-deploy workflow +- **Resource limits**: Proper CPU/memory management for agents +- **Secrets management**: Sealed Secrets for git-safe secrets +- **Observability**: Built-in metrics, logs aggregation + +--- + +### Step 2.1: Setup K8s on NAS (1-2 days) + +**Prerequisites on UGREEN NAS:** +```bash +# Disable swap (required for K8s) +sudo swapoff -a +sudo sed -i '/ swap / s/^/#/' /etc/fstab + +# Enable required kernel modules +cat < str: + """Spawn an agent as a K8s Job.""" + job_name = f"agent-{agent_id}-{task_id[:8]}" + + # Get agent config (blueprint, role, etc.) + agent_config = self._get_agent_config(agent_id) + + job = client.V1Job( + api_version="batch/v1", + kind="Job", + metadata=client.V1ObjectMeta( + name=job_name, + namespace=self.namespace, + labels={ + "app": "roboco-agent", + "agent-id": agent_id, + "task-id": task_id[:8], + }, + ), + spec=client.V1JobSpec( + ttl_seconds_after_finished=3600, # Cleanup after 1 hour + backoff_limit=0, # No retries + template=client.V1PodTemplateSpec( + metadata=client.V1ObjectMeta( + labels={ + "app": "roboco-agent", + "agent-id": agent_id, + }, + ), + spec=client.V1PodSpec( + restart_policy="Never", + service_account_name="roboco-agent", + containers=[ + client.V1Container( + name="agent", + image=f"ghcr.io/renzof/roboco-agent:{agent_id}", + env=[ + client.V1EnvVar( + name="AGENT_ID", + value=agent_id, + ), + client.V1EnvVar( + name="TASK_ID", + value=task_id, + ), + client.V1EnvVar( + name="INITIAL_PROMPT", + value=initial_prompt, + ), + client.V1EnvVar( + name="API_URL", + value=settings.internal_api_url, + ), + client.V1EnvVar( + name="ANTHROPIC_API_KEY", + value_from=client.V1EnvVarSource( + +secret_key_ref=client.V1SecretKeySelector( + name="roboco-secrets", + key="anthropic-api-key", + ), + ), + ), + ], + resources=client.V1ResourceRequirements( + requests={"memory": "256Mi", "cpu": "100m"}, + limits={"memory": "1Gi", "cpu": "500m"}, + ), + volume_mounts=[ + client.V1VolumeMount( + name="blueprints", + mount_path="/app/agents/blueprints", + read_only=True, + ), + ], + ), + ], + volumes=[ + client.V1Volume( + name="blueprints", + config_map=client.V1ConfigMapVolumeSource( + name="roboco-blueprints", + ), + ), + ], + ), + ), + ), + ) + + try: + self.batch_v1.create_namespaced_job( + namespace=self.namespace, + body=job, + ) + logger.info( + "Spawned agent job", + job_name=job_name, + agent_id=agent_id, + task_id=task_id, + ) + return job_name + except ApiException as e: + logger.error( + "Failed to spawn agent job", + error=str(e), + agent_id=agent_id, + ) + raise + + async def stop_agent(self, agent_id: str) -> None: + """Stop an agent by deleting its Job.""" + # Find jobs for this agent + jobs = self.batch_v1.list_namespaced_job( + namespace=self.namespace, + label_selector=f"agent-id={agent_id}", + ) + + for job in jobs.items: + self.batch_v1.delete_namespaced_job( + name=job.metadata.name, + namespace=self.namespace, + propagation_policy="Background", + ) + logger.info("Deleted agent job", job_name=job.metadata.name) + + async def get_agent_status(self, agent_id: str) -> AgentState: + """Get agent status from Job status.""" + jobs = self.batch_v1.list_namespaced_job( + namespace=self.namespace, + label_selector=f"agent-id={agent_id}", + ) + + if not jobs.items: + return AgentState.STOPPED + + job = jobs.items[-1] # Most recent + if job.status.succeeded: + return AgentState.COMPLETED + elif job.status.failed: + return AgentState.FAILED + elif job.status.active: + return AgentState.ACTIVE + else: + return AgentState.PENDING +``` + +**Config changes:** +```python +# roboco/config.py +class Settings(BaseSettings): + # ... existing settings ... + + # K8s settings + k8s_namespace: str = "roboco" + k8s_agent_image: str = "ghcr.io/renzof/roboco-agent" + k8s_agent_memory_request: str = "256Mi" + k8s_agent_memory_limit: str = "1Gi" + k8s_job_ttl: int = 3600 # seconds +``` + +--- + +### Step 2.7: Secrets with Sealed Secrets (1 day) + +**Install kubeseal CLI:** +```bash +# macOS +brew install kubeseal + +# Linux +wget https://github.com/bitnami-labs/sealed-secrets/releases/download/v0.24.5/ku +beseal-0.24.5-linux-amd64.tar.gz +tar -xvf kubeseal-*.tar.gz +sudo mv kubeseal /usr/local/bin/ +``` + +**Install controller:** +```bash +kubectl apply -f https://github.com/bitnami-labs/sealed-secrets/releases/downloa +d/v0.24.5/controller.yaml +``` + +**Create secrets:** +```yaml +# secrets.yaml (NOT committed to git) +apiVersion: v1 +kind: Secret +metadata: +name: roboco-secrets +namespace: roboco +type: Opaque +stringData: +postgres-user: roboco +postgres-password: your-secure-password +database-url: postgresql://roboco:your-secure-password@postgres:5432/roboco +anthropic-api-key: sk-ant-xxx +redis-url: redis://redis:6379 +``` + +**Seal the secrets:** +```bash +kubeseal --format yaml < secrets.yaml > +deploy/overlays/production/sealed-secrets.yaml +rm secrets.yaml # Don't keep plaintext! +``` + +--- + +### Step 2.8: ArgoCD Applications (1 day) + +```yaml +# deploy/argocd/apps/roboco.yaml +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: +name: roboco +namespace: argocd +finalizers: + - resources-finalizer.argocd.argoproj.io +spec: +project: default + +source: + repoURL: git@github.com:renzof/roboco.git + targetRevision: main + path: deploy/overlays/production + +destination: + server: https://kubernetes.default.svc + namespace: roboco + +syncPolicy: + automated: + prune: true + selfHeal: true + allowEmpty: false + syncOptions: + - CreateNamespace=true + - PrunePropagationPolicy=foreground + retry: + limit: 5 + backoff: + duration: 5s + factor: 2 + maxDuration: 3m + +# Health checks +ignoreDifferences: + - group: apps + kind: Deployment + jsonPointers: + - /spec/replicas # Allow HPA to manage +``` + +**Apply:** +```bash +kubectl apply -f deploy/argocd/apps/roboco.yaml +``` + +--- + +### Step 2.9: Testing & Validation (2-3 days) + +**Validation checklist:** + +```bash +# 1. Verify all pods running +kubectl get pods -n roboco +# Expected: postgres-0, redis-xxx, qdrant-0, roboco-api-xxx (x2), +roboco-orchestrator-xxx + +# 2. Check PostgreSQL +kubectl exec -it postgres-0 -n roboco -- psql -U roboco -c "SELECT 1" + +# 3. Check Redis +kubectl exec -it deploy/redis -n roboco -- redis-cli ping + +# 4. Check API health +kubectl port-forward svc/roboco-api -n roboco 8000:8000 & +curl http://localhost:8000/health + +# 5. Check API endpoints +curl http://localhost:8000/api/v1/agents +curl http://localhost:8000/api/v1/tasks + +# 6. Test agent spawning +# Trigger a task that spawns an agent +curl -X POST http://localhost:8000/api/v1/tasks \ +-H "Content-Type: application/json" \ +-d '{"title": "Test task", "team": "backend"}' + +# Watch for agent job +kubectl get jobs -n roboco -w + +# 7. Check ArgoCD sync +argocd app get roboco +argocd app sync roboco + +# 8. Test GitOps flow +git commit --allow-empty -m "test: trigger deploy" +git push +# Watch ArgoCD sync +``` + +**Monitoring setup:** +```bash +# Install metrics-server +kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/late +st/download/components.yaml + +# View resource usage +kubectl top pods -n roboco +kubectl top nodes +``` + +--- + +## Critical Files to Modify + +### Phase 1 Files: +| File | Change | +|------|--------| +| `roboco/mcp/utils.py` | Extract `ApiClient` → SDK | +| `roboco/api/schemas/*.py` | Copy models to SDK | +| `agents/blueprints/` | Genericize and move to OSS | + +### Phase 2 Files: +| File | Change | +|------|--------| +| `roboco/runtime/orchestrator.py` | Docker CLI → K8s API | +| `roboco/config.py` | Add K8s settings | +| `docker/orchestrator.Dockerfile` | Add `kubernetes` package | +| NEW: `deploy/` | All K8s manifests | + +--- + +## Git Strategy + +**Monorepo approach:** +``` +roboco/ # Private repo +├── roboco/ # Python source +├── deploy/ # K8s manifests (ArgoCD watches this) +│ ├── base/ +│ └── overlays/production/ +└── docker/ + +roboco-agents/ # Public repo (separate) +├── sdk/ +├── cli/ +├── blueprints/ +└── docs/ +``` + +ArgoCD flow: `git push` → ArgoCD detects → auto-sync → pods updated + +--- + +## Timeline Summary + +| Phase | Task | Days | +|-------|------|------| +| **1.1** | Create OSS repo | 1 | +| **1.2** | Extract SDK | 2-3 | +| **1.3** | Create CLI | 2-3 | +| **1.4** | Package blueprints | 1 | +| **1.5** | Restructure private repo | 1-2 | +| | **Phase 1 Total** | **7-10** | +| **2.1** | Setup K8s on NAS | 1-2 | +| **2.2** | Install ArgoCD | 1 | +| **2.3** | NFS storage | 1 | +| **2.4** | K8s manifests | 2-3 | +| **2.5** | RBAC setup | (included) | +| **2.6** | Orchestrator K8s migration | 2-3 | +| **2.7** | Sealed Secrets | 1 | +| **2.8** | ArgoCD apps | 1 | +| **2.9** | Testing & validation | 2-3 | +| | **Phase 2 Total** | **12-16** | +| | **Grand Total** | **19-26 days** | + +--- + +## Dependencies & Prerequisites + +### Before Phase 1: +- [ ] GitHub account for `roboco-agents` repo +- [ ] PyPI account for publishing SDK +- [ ] Decide on license (MIT vs Apache 2.0) + +### Before Phase 2: +- [ ] UGREEN NAS accessible via SSH +- [ ] NAS has Docker removed or disabled +- [ ] SSH key for GitHub access from NAS +- [ ] Anthropic API key for production + +--- + +## Risk Mitigation + +| Risk | Mitigation | +|------|------------| +| K8s learning curve | Start with minimal manifests, iterate | +| Data loss during migration | Keep Docker Compose as fallback | +| Network issues between pods | Use ClusterIP services, test connectivity | +| Secret leakage | Use Sealed Secrets from day 1 | + +--- + +## Success Criteria + +### Phase 1 Complete When: +- [ ] `pip install roboco-sdk` works from PyPI +- [ ] `roboco task list` CLI works +- [ ] Community can build agents with templates +- [ ] Private repo has no SDK dependencies + +### Phase 2 Complete When: +- [ ] `kubectl get pods -n roboco` shows all healthy +- [ ] Agent jobs spawn and complete successfully +- [ ] `git push` triggers ArgoCD deploy +- [ ] API accessible from outside cluster +- [ ] Zero downtime during rolling updates + +--- + +## Phase 3: Production Hardening + +### Step 3.1: Ingress & External Access + +```yaml +# deploy/base/ingress/ingress.yaml +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: +name: roboco-ingress +namespace: roboco +annotations: + nginx.ingress.kubernetes.io/proxy-body-size: "50m" + cert-manager.io/cluster-issuer: "letsencrypt-prod" +spec: +ingressClassName: nginx +tls: +- hosts: + - api.roboco.local + secretName: roboco-tls +rules: +- host: api.roboco.local + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: roboco-api + port: + number: 8000 +``` + +**Install nginx ingress:** +```bash +helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx +helm install ingress-nginx ingress-nginx/ingress-nginx \ +--namespace ingress-nginx --create-namespace \ +--set controller.service.type=NodePort \ +--set controller.service.nodePorts.http=30080 \ +--set controller.service.nodePorts.https=30443 +``` + +--- + +### Step 3.2: Monitoring Stack (Prometheus + Grafana) + +```bash +# Install kube-prometheus-stack +helm repo add prometheus-community +https://prometheus-community.github.io/helm-charts +helm install monitoring prometheus-community/kube-prometheus-stack \ +--namespace monitoring --create-namespace \ +--set grafana.adminPassword=your-password \ +--set prometheus.prometheusSpec.retention=30d \ +--set prometheus.prometheusSpec.storageSpec.volumeClaimTemplate.spec.storageCl +assName=nfs-roboco \ +--set prometheus.prometheusSpec.storageSpec.volumeClaimTemplate.spec.resources +.requests.storage=20Gi +``` + +**ServiceMonitor for RoboCo API:** +```yaml +# deploy/base/monitoring/servicemonitor.yaml +apiVersion: monitoring.coreos.com/v1 +kind: ServiceMonitor +metadata: +name: roboco-api +namespace: roboco +spec: +selector: + matchLabels: + app: roboco-api +endpoints: +- port: http + path: /metrics + interval: 30s +``` + +**Grafana dashboards to create:** +- API request latency & throughput +- Agent job success/failure rates +- Task lifecycle metrics +- Database connection pool +- Memory/CPU per component + +--- + +### Step 3.3: Log Aggregation (Loki) + +```bash +helm repo add grafana https://grafana.github.io/helm-charts +helm install loki grafana/loki-stack \ +--namespace monitoring \ +--set promtail.enabled=true \ +--set loki.persistence.enabled=true \ +--set loki.persistence.storageClassName=nfs-roboco \ +--set loki.persistence.size=20Gi +``` + +**Add to Grafana datasources** for querying logs like: +``` +{namespace="roboco", app="roboco-api"} |= "error" +{namespace="roboco"} | json | level="ERROR" +``` + +--- + +### Step 3.4: CI/CD Pipeline (GitHub Actions) + +```yaml +# .github/workflows/build-and-push.yaml +name: Build and Push Images + +on: +push: + branches: [main] + paths: + - 'roboco/**' + - 'docker/**' + +env: +REGISTRY: ghcr.io +IMAGE_NAME: ${{ github.repository }} + +jobs: +build-api: + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + steps: + - uses: actions/checkout@v4 + + - name: Log in to Container Registry + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Build and push API image + uses: docker/build-push-action@v5 + with: + context: . + file: docker/api.Dockerfile + push: true + tags: | + ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-api:${{ github.sha }} + ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-api:latest + + - name: Update Kustomize image tag + run: | + cd deploy/overlays/production + kustomize edit set image ghcr.io/renzof/roboco-api:${{ github.sha }} + git config user.name "GitHub Actions" + git config user.email "actions@github.com" + git add . + git commit -m "chore: update image to ${{ github.sha }}" + git push + +build-orchestrator: + runs-on: ubuntu-latest + # Similar to above... + +build-agent: + runs-on: ubuntu-latest + # Build base agent image... +``` + +--- + +### Step 3.5: Backup & Restore + +**PostgreSQL backup CronJob:** +```yaml +# deploy/base/postgres/backup-cronjob.yaml +apiVersion: batch/v1 +kind: CronJob +metadata: +name: postgres-backup +namespace: roboco +spec: +schedule: "0 2 * * *" # Daily at 2 AM +jobTemplate: + spec: + template: + spec: + containers: + - name: backup + image: postgres:16 + command: + - /bin/sh + - -c + - | + pg_dump -h postgres -U roboco roboco | gzip > +/backups/roboco-$(date +%Y%m%d).sql.gz + # Keep last 7 days + find /backups -name "*.sql.gz" -mtime +7 -delete + env: + - name: PGPASSWORD + valueFrom: + secretKeyRef: + name: roboco-secrets + key: postgres-password + volumeMounts: + - name: backups + mountPath: /backups + volumes: + - name: backups + persistentVolumeClaim: + claimName: postgres-backups + restartPolicy: OnFailure +``` + +**Restore procedure:** +```bash +# 1. Scale down API and orchestrator +kubectl scale deployment roboco-api --replicas=0 -n roboco +kubectl scale deployment roboco-orchestrator --replicas=0 -n roboco + +# 2. Restore from backup +kubectl exec -it postgres-0 -n roboco -- bash -c \ +"gunzip -c /backups/roboco-20241225.sql.gz | psql -U roboco roboco" + +# 3. Scale back up +kubectl scale deployment roboco-api --replicas=2 -n roboco +kubectl scale deployment roboco-orchestrator --replicas=1 -n roboco +``` + +--- + +### Step 3.6: Rollback Procedures + +**ArgoCD rollback:** +```bash +# View history +argocd app history roboco + +# Rollback to previous version +argocd app rollback roboco + +# Or via kubectl +kubectl rollout undo deployment/roboco-api -n roboco +kubectl rollout undo deployment/roboco-orchestrator -n roboco +``` + +**Emergency rollback script:** +```bash +#!/bin/bash +# scripts/emergency-rollback.sh +set -e + +REVISION=${1:-1} # Default: rollback 1 revision + +echo "Rolling back roboco-api..." +kubectl rollout undo deployment/roboco-api -n roboco --to-revision=$REVISION + +echo "Rolling back roboco-orchestrator..." +kubectl rollout undo deployment/roboco-orchestrator -n roboco +--to-revision=$REVISION + +echo "Waiting for rollout..." +kubectl rollout status deployment/roboco-api -n roboco +kubectl rollout status deployment/roboco-orchestrator -n roboco + +echo "Rollback complete!" +``` + +--- + +## Transition Strategy (Docker → K8s) + +### Week 1: Parallel Running +1. Deploy K8s stack alongside existing Docker Compose +2. Point K8s to SAME database (careful!) +3. Test API endpoints, verify responses match +4. Run shadow traffic (duplicate requests to both) + +### Week 2: Gradual Cutover +1. Update DNS/ingress to point 10% traffic to K8s +2. Monitor error rates and latency +3. Increase to 50%, then 100% +4. Keep Docker Compose as fallback + +### Week 3: Cleanup +1. Verify all agents running on K8s +2. Shut down Docker Compose services +3. Migrate remaining data if any +4. Remove Docker Compose files (keep in git history) + +--- + +## Cost Estimation (Self-Hosted) + +| Resource | Usage | Notes | +|----------|-------|-------| +| UGREEN NAS | Already owned | 36TB storage | +| Electricity | ~50W avg | ~$5/month | +| Anthropic API | Variable | Based on agent usage | +| Domain (optional) | $12/year | For external access | +| **Total** | **~$5-10/month** | Excluding API costs | + +--- + +## What's NOT Included (Future) + +- [ ] Multi-node K8s cluster (HA) +- [ ] External database (RDS/Cloud SQL) +- [ ] CDN for static assets +- [ ] Rate limiting / API gateway +- [ ] Multi-region deployment +- [ ] Disaster recovery site \ No newline at end of file diff --git a/docs/workflows/AGENT_CHEATSHEET.md b/docs/workflows/AGENT_CHEATSHEET.md new file mode 100644 index 00000000..00c7a0b4 --- /dev/null +++ b/docs/workflows/AGENT_CHEATSHEET.md @@ -0,0 +1,214 @@ +# Agent Cheatsheet + +Quick reference for each role. + +--- + +## Developer (be-dev-1, be-dev-2, fe-dev-1, fe-dev-2, ux-dev) + +### Your Flow +``` +SCAN → CLAIM → PLAN → START → WORK → VERIFY → SUBMIT_QA +``` + +### Your Tools +``` +✅ roboco_task_scan(team="backend") +✅ roboco_task_get(task_id) +✅ roboco_task_claim(task_id) +✅ roboco_task_plan(task_id, approach, steps, risks?, open_questions?) +✅ roboco_task_start(task_id) +✅ roboco_task_progress(task_id, message, percentage) +✅ roboco_task_block(task_id, blocker_task_id) +✅ roboco_task_pause(task_id, reason, checkpoint, remaining_work) +✅ roboco_task_escalate(task_id, reason) +✅ roboco_task_submit_verification(task_id) +✅ roboco_task_submit_qa(task_id, notes) + +✅ roboco_message_send(channel, content, task_id) +✅ roboco_channel_history(channel) +✅ roboco_notify_list() +✅ roboco_notify_ack(notification_id) + +✅ roboco_journal_entry(type, title, content, task_id) +✅ roboco_journal_reflect(...) +✅ roboco_journal_learning(...) +✅ roboco_journal_struggle(...) +✅ roboco_journal_search(query) +``` + +### NOT Your Tools +``` +❌ roboco_task_create → PM only +❌ roboco_task_assign → PM only +❌ roboco_task_activate → PM only +❌ roboco_task_complete → PM only +❌ roboco_task_unblock → PM only (you can unblock your OWN) +❌ roboco_task_qa_pass → QA only +❌ roboco_task_qa_fail → QA only +❌ roboco_task_docs_complete → Documenter only +❌ roboco_notify_send → PM only +``` + +--- + +## QA (be-qa, fe-qa, ux-qa) + +### Your Flow +``` +SCAN (awaiting_qa) → CLAIM → START → REVIEW → PASS or FAIL +``` + +### Your Tools +``` +✅ roboco_task_scan(team="backend") → Look for awaiting_qa +✅ roboco_task_get(task_id) +✅ roboco_task_claim(task_id) → Only from awaiting_qa +✅ roboco_task_start(task_id) +✅ roboco_task_progress(task_id, message, percentage) +✅ roboco_task_qa_pass(task_id, notes) +✅ roboco_task_qa_fail(task_id, notes, issues) +✅ roboco_task_escalate(task_id, reason) + +✅ roboco_message_send(...) +✅ roboco_channel_history(...) +✅ roboco_journal_entry(...) +``` + +### Rules +``` +⚠️ Cannot QA tasks you developed (self-review prevention) +⚠️ Can only claim from awaiting_qa status +``` + +--- + +## Documenter (be-doc, fe-doc, ux-doc) + +### Your Flow +``` +SCAN (awaiting_documentation) → CLAIM → START → WRITE → DOCS_COMPLETE +``` + +### Your Tools +``` +✅ roboco_task_scan(team="backend") → Look for awaiting_documentation +✅ roboco_task_get(task_id) +✅ roboco_task_claim(task_id) → From awaiting_documentation or pending +✅ roboco_task_start(task_id) +✅ roboco_task_progress(task_id, message, percentage) +✅ roboco_task_docs_complete(task_id) + +✅ roboco_journal_read_team(agent_slug) → Read dev's journey +✅ roboco_message_send(...) +✅ roboco_channel_history(...) +✅ roboco_journal_entry(...) +``` + +### Rules +``` +⚠️ Cannot document tasks you developed (self-review prevention) +⚠️ Read developer's journal for context +``` + +--- + +## Cell PM (be-pm, fe-pm, ux-pm) + +### Your Flow +``` +SCAN → CLAIM → START → PLAN → CREATE SUBTASKS → ACTIVATE → NOTIFY → MONITOR → COMPLETE +``` + +### Your Tools +``` +✅ roboco_task_scan(team="backend") +✅ roboco_task_get(task_id) +✅ roboco_task_claim(task_id) +✅ roboco_task_start(task_id) +✅ roboco_task_plan(task_id, approach, steps) +✅ roboco_task_progress(task_id, message, percentage) +✅ roboco_task_create(data) → Create subtasks +✅ roboco_task_assign(task_id, agent) → Assign to cell members +✅ roboco_task_activate(task_id) → backlog → pending +✅ roboco_task_complete(task_id) → Final completion +✅ roboco_task_unblock(task_id) → Unblock any cell task +✅ roboco_task_pause(task_id, ...) +✅ roboco_task_cancel(task_id, reason) + +✅ roboco_session_create_for_tasks(data) +✅ roboco_session_link_task(data) +✅ roboco_group_create(data) + +✅ roboco_notify_send(recipient, type, task_id, message) +✅ roboco_notify_list() +✅ roboco_notify_ack(notification_id) + +✅ roboco_journal_read_team(agent_slug) → Read cell member journals +✅ roboco_message_send(...) +✅ roboco_channel_history(...) +``` + +### Your Channels +``` +#backend-cell (or #frontend-cell, #uxui-cell) +#pm-all +#dev-all (read/write) +#qa-all (read/write) +#doc-all (read/write) +``` + +--- + +## Main PM + +### Your Flow +``` +RECEIVE FROM BOARD → TRIAGE → CREATE CELL SUBTASKS → CREATE SESSION → ACTIVATE → NOTIFY CELL PMs → MONITOR → COMPLETE +``` + +### Your Tools +All Cell PM tools PLUS: +``` +✅ Can work across ALL cells +✅ Can notify anyone +✅ Coordinates cross-cell work +``` + +### Your Channels +``` +#main-pm-board +#pm-all +All cell channels (read) +#announcements (write) +``` + +--- + +## Quick Status Reference + +``` +BACKLOG ────activate────► PENDING ────claim────► CLAIMED ────start────► IN_PROGRESS + │ + ┌───────────────────────────────────────────────────┤ + │ │ + BLOCKED/PAUSED (working) + │ │ + └───────────────────────────────────────────────────┤ + │ + ────verify────► VERIFYING + │ + ────submit_qa──► AWAITING_QA + │ + ┌──────────────────────────────┤ + │ │ + (qa_fail) (qa_pass) + │ │ + NEEDS_REVISION AWAITING_DOCUMENTATION + │ │ + └──────────────────────────────┤ + │ + ────docs_complete──► AWAITING_PM_REVIEW + │ + ────complete────► COMPLETED +``` diff --git a/docs/workflows/COMMUNICATION.md b/docs/workflows/COMMUNICATION.md new file mode 100644 index 00000000..7d338669 --- /dev/null +++ b/docs/workflows/COMMUNICATION.md @@ -0,0 +1,149 @@ +# Communication Guide + +## Communication vs Notifications + +| Aspect | Communication (Messages) | Notifications | +|--------|--------------------------|---------------| +| Nature | Constant stream | Formal signals | +| Who can send | Everyone (in allowed channels) | PM/Board/Auditor only | +| Acknowledgment | Not required | Often required | +| Purpose | Ambient awareness, discussion | Demand attention | +| Tool | `roboco_message_send` | `roboco_notify_send` | + +--- + +## Sending Messages + +### Basic Message + +```python +roboco_message_send({ + "channel": "backend-cell", + "content": "Starting work on the rate limiter. Will update as I progress.", + "task_id": "uuid-here" # REQUIRED - links to task's session +}) +``` + +### Message with Mentions + +```python +roboco_message_send({ + "channel": "backend-cell", + "content": "@be-pm Need clarification on acceptance criteria for edge case X", + "task_id": "uuid-here", + "mentions": ["be-pm"] # Mentioned agents get notified +}) +``` + +### Message Types + +```python +roboco_message_send({ + "channel": "backend-cell", + "content": "Found a potential security issue in auth flow", + "task_id": "uuid-here", + "message_type": "alert" # Types: message, question, alert, update +}) +``` + +--- + +## Reading Channel History + +```python +roboco_channel_history( + channel="backend-cell", + limit=20, # Max messages to return + hours_back=24 # How far back to look +) +``` + +--- + +## Channel Access + +### Your Channels by Role + +| Role | Read | Write | +|------|------|-------| +| **be-dev-1/2** | backend-cell, dev-all | backend-cell, dev-all | +| **be-qa** | backend-cell, qa-all | backend-cell, qa-all | +| **be-pm** | backend-cell, pm-all, dev-all, qa-all, doc-all | all of these | +| **be-doc** | backend-cell, doc-all | backend-cell, doc-all | +| **main-pm** | all channels | pm-all, announcements | +| **auditor** | ALL (silent) | none | + +### List Your Channels + +```python +roboco_channel_list() +# Returns: readable_channels, writable_channels +``` + +--- + +## Message Routing + +Messages are routed through **sessions**: + +``` +Channel → Group → Session → Messages +``` + +**IMPORTANT:** Always include `task_id` when sending messages. This routes the message to the correct session linked to that task. + +### If Task Has No Session + +``` +ERROR: NO_SESSION_FOR_TASK +Message: "Task has no linked session" +``` + +**Solution:** Escalate to PM to create session: +```python +roboco_task_escalate(task_id, "Task needs session created") +``` + +--- + +## When to Message vs Notify + +| Situation | Use | +|-----------|-----| +| Progress update | Message | +| Question for teammate | Message with mention | +| Found a blocker | Message + `roboco_task_block()` | +| Need PM decision | `roboco_task_escalate()` | +| Assigning work | Notification (PM only) | +| Urgent alert | Notification (PM/Board only) | + +--- + +## Message Best Practices + +1. **Always include task_id** - Required for routing +2. **Use mentions** - Get specific attention +3. **Be concise** - Others are busy +4. **Use message_type** - Helps categorization +5. **Update regularly** - Keep cell informed of progress + +--- + +## Cross-Cell Communication + +Developers/QA/Docs cannot message other cells directly. + +**To communicate cross-cell:** +1. Message your Cell PM +2. Cell PM coordinates with other Cell PM +3. Or use cross-cell channels (dev-all, qa-all) for general discussion + +```python +# Developer asking for frontend input +roboco_message_send({ + "channel": "dev-all", + "content": "Question for frontend devs: What format do you expect for the user API response?", + "task_id": "uuid-here", + "message_type": "question" +}) +``` diff --git a/docs/workflows/DEVELOPER.md b/docs/workflows/DEVELOPER.md new file mode 100644 index 00000000..3f0a4901 --- /dev/null +++ b/docs/workflows/DEVELOPER.md @@ -0,0 +1,168 @@ +# Developer Workflow + +## Overview + +Developers (be-dev-1, be-dev-2, fe-dev-1, fe-dev-2, ux-dev) execute implementation tasks. + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ DEVELOPER WORKFLOW │ +└─────────────────────────────────────────────────────────────────────────┘ + +1. CHECK NOTIFICATIONS + │ + │ roboco_notify_list() + │ roboco_notify_ack(notification_id) + │ + ▼ +2. SCAN FOR WORK + │ + │ roboco_task_scan(team="backend") + │ + │ Look for: + │ ├── Tasks in "pending" assigned to ME + │ ├── Tasks in "pending" unassigned (can claim) + │ └── My paused tasks (should resume) + │ + ▼ +3. CLAIM TASK + │ + │ roboco_task_claim(task_id) + │ + │ ┌─────────────────────────────────────────────────────────────────┐ + │ │ BEFORE: │ + │ │ status: pending │ + │ │ assigned_to: null OR my_id (if PM pre-assigned) │ + │ │ │ + │ │ AFTER: │ + │ │ status: claimed │ + │ │ assigned_to: my_id │ + │ │ claimed_at: now │ + │ └─────────────────────────────────────────────────────────────────┘ + │ + ▼ +4. PLAN + │ + │ roboco_task_plan( + │ task_id, + │ approach: "How I'll solve this", + │ steps: [ + │ {title: "Step 1", description: "..."}, + │ {title: "Step 2", description: "..."} + │ ], + │ risks: ["Potential issue X"], + │ open_questions: ["Need to clarify Y"] + │ ) + │ + │ If questions → roboco_message_send() to PM + │ + ▼ +5. START WORK + │ + │ roboco_task_start(task_id) + │ + │ STATUS: claimed → in_progress + │ + ▼ +6. EXECUTE (loop) + │ + │ ┌─────────────────────────────────────────────────────────────────┐ + │ │ While working: │ + │ │ │ + │ │ roboco_task_progress(task_id, "Completed X", 25) │ + │ │ roboco_task_progress(task_id, "Working on Y", 50) │ + │ │ roboco_task_progress(task_id, "Almost done", 75) │ + │ │ │ + │ │ roboco_journal_entry({ │ + │ │ type: "work_log", │ + │ │ content: "What I did and learned" │ + │ │ }) │ + │ │ │ + │ │ If BLOCKED: │ + │ │ roboco_task_block(task_id, blocker_task_id) ← blocked by │ + │ │ OR another task │ + │ │ roboco_task_escalate(task_id, reason) ← need PM help │ + │ │ │ + │ │ If need to PAUSE: │ + │ │ roboco_task_pause(task_id, reason, checkpoint, remaining) │ + │ └─────────────────────────────────────────────────────────────────┘ + │ + ▼ +7. SELF-VERIFY + │ + │ roboco_task_submit_verification(task_id) + │ + │ STATUS: in_progress → verifying + │ + │ ┌─────────────────────────────────────────────────────────────────┐ + │ │ Self-check: │ + │ │ ├── Does it meet acceptance criteria? │ + │ │ ├── Did I run tests? │ + │ │ ├── Is the code clean? │ + │ │ └── Are my notes complete? │ + │ └─────────────────────────────────────────────────────────────────┘ + │ + ▼ +8. SUBMIT FOR QA + │ + │ roboco_task_submit_qa(task_id, { + │ notes: "What I built and how to test it", + │ test_instructions: "Steps to verify" + │ }) + │ + │ STATUS: verifying → awaiting_qa + │ + ▼ + DONE (for developer) → QA takes over +``` + +## If QA Fails + +``` +QA FAILS: + awaiting_qa ──qa_fail──► needs_revision + +DEVELOPER SEES IT: + roboco_task_scan() shows "needs_revision" task + +DEVELOPER CLAIMS AGAIN: + roboco_task_claim(task_id) + STATUS: needs_revision → claimed + +DEVELOPER FIXES: + roboco_task_start(task_id) + ... fix issues ... + roboco_task_submit_verification(task_id) + roboco_task_submit_qa(task_id, notes) +``` + +## Status Transitions (Developer perspective) + +``` +CAN CLAIM: + pending ──────────► claimed + needs_revision ───► claimed + +AFTER CLAIM: + claimed ──start──► in_progress + +WHILE WORKING: + in_progress ──block──► blocked + in_progress ──pause──► paused + blocked ─────unblock─► in_progress + paused ──────resume──► in_progress + +SUBMIT: + in_progress ──verify──► verifying + verifying ───submit_qa──► awaiting_qa +``` + +## Key Rules + +1. **CLAIM before anything** - Must claim to own the task +2. **PLAN before START** - roboco_task_plan() required before start() +3. **PROGRESS updates** - Keep PM informed with percentage +4. **JOURNAL your work** - Document decisions, learnings, struggles +5. **SELF-VERIFY first** - Check your own work before QA +6. **Cannot COMPLETE** - Only PM completes tasks after full workflow +7. **One task at a time** - Can't claim new task while one is in_progress diff --git a/docs/workflows/DOCUMENTER.md b/docs/workflows/DOCUMENTER.md new file mode 100644 index 00000000..bfaaaab0 --- /dev/null +++ b/docs/workflows/DOCUMENTER.md @@ -0,0 +1,112 @@ +# Documenter Workflow + +## Overview + +Documenters (be-doc, fe-doc, ux-doc) create production documentation from developer work. + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ DOCUMENTER WORKFLOW │ +└─────────────────────────────────────────────────────────────────────────┘ + +1. SCAN FOR WORK + │ + │ roboco_task_scan(team="backend") + │ + │ Look for: + │ └── Tasks in "awaiting_documentation" status + │ + ▼ +2. CLAIM TASK + │ + │ roboco_task_claim(task_id) + │ + │ ┌─────────────────────────────────────────────────────────────────┐ + │ │ Documenter can claim: │ + │ │ ├── "awaiting_documentation" (normal workflow) │ + │ │ └── "pending" (direct docs tasks from PM) │ + │ │ │ + │ │ AFTER: │ + │ │ status: claimed │ + │ │ assigned_to: documenter │ + │ └─────────────────────────────────────────────────────────────────┘ + │ + ▼ +3. START DOCUMENTATION + │ + │ roboco_task_start(task_id) + │ + │ STATUS: claimed → in_progress + │ + ▼ +4. GATHER CONTEXT + │ + │ ┌─────────────────────────────────────────────────────────────────┐ + │ │ Read: │ + │ │ ├── Developer's handoff notes (in quick_context) │ + │ │ ├── Developer's journal entries │ + │ │ ├── QA review notes │ + │ │ ├── Related commits │ + │ │ └── Code changes │ + │ │ │ + │ │ roboco_journal_read_team("be-dev-1") → Read dev's journal │ + │ │ roboco_channel_history("backend-cell") → Related discussion │ + │ └─────────────────────────────────────────────────────────────────┘ + │ + ▼ +5. WRITE DOCUMENTATION + │ + │ ┌─────────────────────────────────────────────────────────────────┐ + │ │ Create: │ + │ │ ├── API documentation │ + │ │ ├── Usage examples │ + │ │ ├── Architecture notes │ + │ │ └── Update README if needed │ + │ │ │ + │ │ roboco_task_progress(task_id, "Writing API docs", 50) │ + │ │ roboco_task_progress(task_id, "Adding examples", 75) │ + │ └─────────────────────────────────────────────────────────────────┘ + │ + ▼ +6. COMPLETE DOCUMENTATION + │ + │ roboco_task_docs_complete(task_id) + │ + │ STATUS: in_progress → awaiting_pm_review + │ + ▼ + DONE (for documenter) → PM reviews and completes +``` + +## Self-Documentation Prevention + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ Documenter CANNOT document tasks they originally developed │ +│ │ +│ System tracks original_developer in quick_context │ +│ If documenter == original_developer → FORBIDDEN │ +└─────────────────────────────────────────────────────────────────────────┘ +``` + +## Status Transitions (Documenter perspective) + +``` +CAN CLAIM: + awaiting_documentation ──claim──► claimed + pending ─────────────────claim──► claimed (direct docs tasks) + +AFTER CLAIM: + claimed ──start──► in_progress + +COMPLETE: + in_progress ──docs_complete──► awaiting_pm_review +``` + +## Key Rules + +1. **Only claim awaiting_documentation or pending** - Can't claim dev tasks +2. **Cannot self-document** - Can't document your own dev work +3. **Read developer's journey** - Use journals and handoff notes +4. **Quality docs** - Future developers depend on this +5. **Cannot COMPLETE task** - Only submits for PM review diff --git a/docs/workflows/ESCALATION.md b/docs/workflows/ESCALATION.md new file mode 100644 index 00000000..46f1044b --- /dev/null +++ b/docs/workflows/ESCALATION.md @@ -0,0 +1,184 @@ +# Escalation Guide + +## Escalation Chain + +``` +Developer/QA/Documenter + │ + ▼ + Cell PM + │ + ▼ + Main PM + │ + ▼ + Product Owner + │ + ▼ + CEO +``` + +--- + +## When to Escalate + +| Situation | Escalate To | Tool | +|-----------|-------------|------| +| Need PM decision | Cell PM | `roboco_task_escalate` | +| Blocked by external factor | Cell PM | `roboco_task_escalate` | +| Cross-cell coordination needed | Cell PM → Main PM | `roboco_task_escalate` | +| Scope creep beyond task | Cell PM | `roboco_task_escalate` | +| Resource/priority conflict | Cell PM | `roboco_task_escalate` | +| Cell PM unresponsive | Main PM | `roboco_task_escalate` | +| Company-wide issue | Product Owner | `roboco_escalate` (PM only) | + +--- + +## Escalation Tools + +### For All Agents: `roboco_task_escalate` + +Escalate a task-related issue: + +```python +roboco_task_escalate( + task_id="uuid-here", + reason="Need clarification on API contract - acceptance criteria unclear", + escalate_to="be-pm" # Optional - auto-routes if omitted +) +``` + +**Auto-routing (when `escalate_to` omitted):** +- Developer/QA/Doc → Cell PM +- Cell PM → Main PM +- Main PM → Product Owner + +### For PM/Board Only: `roboco_escalate` + +General escalation (not task-specific): + +```python +roboco_escalate( + escalate_to="main-pm", + subject="Need cross-cell coordination", + description="Backend and frontend teams need to sync on API changes", + task_id="uuid-optional" # Optional link +) +``` + +--- + +## Escalation Reasons by Role + +### Developer Escalations + +| Reason | Example | +|--------|---------| +| Unclear requirements | "Acceptance criteria doesn't specify error handling" | +| Blocked by other task | "Waiting on auth service from fe-dev-1" | +| Scope question | "Should I also handle edge case X?" | +| Need decision | "Two valid approaches - need PM guidance" | +| Technical blocker | "Can't reproduce bug in dev environment" | + +### QA Escalations + +| Reason | Example | +|--------|---------| +| Can't reproduce | "Bug not reproducible with given steps" | +| Unclear test criteria | "Don't know what 'acceptable performance' means" | +| Blocking issue found | "Critical security flaw - should we halt?" | +| Test environment issue | "Staging is down, can't proceed" | + +### Cell PM Escalations + +| Reason | Example | +|--------|---------| +| Cross-cell dependency | "Need frontend to expose new endpoint" | +| Resource conflict | "Both tasks need be-dev-1, can't parallelize" | +| Priority question | "Two P1 tasks - which first?" | +| Scope change | "Requirements changed mid-sprint" | + +--- + +## What Happens When You Escalate + +1. **Escalation notification sent** to target +2. **Task status unchanged** (you can keep working if possible) +3. **Escalation logged** in task history +4. **Target must ACK** the escalation +5. **Resolution tracked** when target responds + +--- + +## Escalation vs Block vs Pause + +| Action | When | Effect | +|--------|------|--------| +| **Escalate** | Need help/decision | Notifies PM, you can continue | +| **Block** | Waiting on another task | Status → blocked, can claim other work | +| **Pause** | Need to stop temporarily | Status → paused, state saved | + +### Combining Actions + +Often you'll combine: + +```python +# Blocked AND need PM help +roboco_task_block(task_id, blocker_task_id) +roboco_task_escalate(task_id, "Blocked on auth service, need PM to coordinate") +``` + +--- + +## Good Escalation Format + +```python +roboco_task_escalate( + task_id="uuid-here", + reason=""" +ISSUE: API contract unclear +CONTEXT: Implementing user endpoint, acceptance criteria says "return user data" +QUESTION: Should I include sensitive fields (email, phone)? What about nested relations? +ATTEMPTED: Checked existing endpoints, no consistent pattern +BLOCKING: Can't proceed without this decision +""" +) +``` + +**Include:** +- What's the issue +- What context you have +- Specific question +- What you already tried +- How it's affecting work + +--- + +## Responding to Escalations (PM) + +When you receive an escalation: + +1. **ACK immediately** - `roboco_notify_ack(notification_id)` +2. **Investigate** - Read task, journals, messages +3. **Decide** - Make the call or escalate further +4. **Communicate** - Message the agent with decision +5. **Unblock if needed** - `roboco_task_unblock(task_id)` + +--- + +## Escalation Anti-Patterns + +❌ **Don't escalate without trying first** +- Check documentation, journals, similar tasks + +❌ **Don't escalate vague issues** +- "I'm stuck" → Instead: "Stuck on X because Y, tried Z" + +❌ **Don't escalate too late** +- Escalate when you recognize you're blocked, not after hours of spinning + +❌ **Don't skip levels** +- Developer → Cell PM → Main PM (don't skip Cell PM) + +❌ **Don't escalate resolved issues** +- Only escalate if you actually need help diff --git a/docs/workflows/GIT_WORKFLOW.md b/docs/workflows/GIT_WORKFLOW.md new file mode 100644 index 00000000..8a51e2ab --- /dev/null +++ b/docs/workflows/GIT_WORKFLOW.md @@ -0,0 +1,199 @@ +# Git Workflow (Future) + +> **Status:** Planned - Not yet implemented +> +> This document describes the intended git workflow for when code tools are added. + +--- + +## Branch Naming + +``` +{type}/{task-id}-{short-description} +``` + +### Types + +| Type | Use | +|------|-----| +| `feature/` | New functionality | +| `fix/` | Bug fixes | +| `refactor/` | Code restructuring | +| `docs/` | Documentation | +| `test/` | Test additions | +| `chore/` | Maintenance | + +### Examples + +``` +feature/TASK-042-rate-limiter +fix/TASK-055-auth-token-expiry +refactor/TASK-067-extract-service +docs/TASK-089-api-documentation +``` + +--- + +## Commit Messages + +``` +{type}({scope}): {description} + +{body} + +Task: {task-id} +Co-authored-by: {agent-name} +``` + +### Types + +| Type | Description | +|------|-------------| +| `feat` | New feature | +| `fix` | Bug fix | +| `docs` | Documentation | +| `style` | Formatting | +| `refactor` | Code restructure | +| `test` | Tests | +| `chore` | Maintenance | +| `perf` | Performance | + +### Example + +``` +feat(auth): add Redis-based rate limiting + +Implements sliding window rate limiter using Redis. +- Configurable limits per endpoint +- Lua script for atomic operations +- Returns rate limit headers + +Task: TASK-042 +Co-authored-by: be-dev-1 +``` + +--- + +## Workflow + +### Developer Flow + +``` +1. CLAIM TASK + │ + ▼ +2. CREATE BRANCH + │ + │ git checkout -b feature/TASK-042-rate-limiter + │ + ▼ +3. WORK & COMMIT + │ + │ # Multiple small commits + │ git commit -m "feat(auth): add rate limit decorator" + │ git commit -m "feat(auth): integrate Redis counter" + │ git commit -m "test(auth): add rate limit tests" + │ + ▼ +4. PUSH BRANCH + │ + │ git push -u origin feature/TASK-042-rate-limiter + │ + ▼ +5. SUBMIT FOR QA + │ + │ roboco_task_submit_qa(task_id, notes) + │ + ▼ +6. QA REVIEWS (on branch) + │ + ├── PASS → Continue + └── FAIL → Fix on same branch, re-push + │ + ▼ +7. CREATE PR (after QA pass) + │ + │ Target: main (or develop) + │ Title: [TASK-042] Add rate limiting + │ Body: Summary + test plan + │ + ▼ +8. PM REVIEWS PR + │ + ▼ +9. MERGE + │ + │ Squash merge preferred + │ + ▼ +10. CLEANUP + │ + │ Delete feature branch +``` + +--- + +## Branch Protection (Main) + +- No direct pushes +- PR required +- QA must pass +- PM approval required +- CI must pass + +--- + +## Commit Frequency + +| Stage | Commit Frequency | +|-------|-----------------| +| During development | Frequently (logical chunks) | +| Before QA | Ensure all changes committed | +| After QA feedback | Fix commits | +| Before merge | Squash if messy | + +--- + +## Handling QA Failures + +``` +QA finds issues + │ + ▼ +Developer gets task back (needs_revision) + │ + ▼ +Developer claims, continues on SAME branch + │ + ▼ +Fix commits: + git commit -m "fix(auth): handle edge case X" + │ + ▼ +Push to same branch + │ + ▼ +Re-submit for QA +``` + +--- + +## Planned Git Tools + +| Tool | Purpose | +|------|---------| +| `roboco_git_branch` | Create task branch | +| `roboco_git_commit` | Create commit with task link | +| `roboco_git_push` | Push to remote | +| `roboco_git_pr` | Create pull request | +| `roboco_git_status` | Check branch state | + +--- + +## Integration with Task System + +When implemented: +- Branch creation linked to task claim +- Commits linked to task in metadata +- PR creation triggers PM review +- Merge triggers completion flow diff --git a/docs/workflows/JOURNALING.md b/docs/workflows/JOURNALING.md new file mode 100644 index 00000000..f9ec65c5 --- /dev/null +++ b/docs/workflows/JOURNALING.md @@ -0,0 +1,193 @@ +# Journaling Guide + +## Purpose + +Your journal is your **personal growth record**. It: +- Documents your decision-making process +- Tracks what you learned +- Records struggles for future reference +- Creates institutional memory +- Helps documenters understand your journey + +--- + +## Journal Entry Types + +### 1. General Entry (`roboco_journal_entry`) + +Basic logging for day-to-day work. + +```python +roboco_journal_entry({ + "type": "work_log", # or "note", "observation" + "title": "Started rate limiter implementation", + "content": "Reviewing existing code patterns in auth module...", + "task_id": "uuid-here", # Link to current task + "tags": ["rate-limiting", "redis"] +}) +``` + +**When to use:** +- Starting work on a task +- Mid-task progress notes +- Observations about the codebase +- General thoughts + +--- + +### 2. Decision Log (`roboco_journal_decision`) + +**REQUIRED** when choosing between approaches. + +```python +roboco_journal_decision({ + "title": "Chose Redis over in-memory for rate limiting", + "context": "Need to implement rate limiting for API endpoints", + "options": [ + "Redis sliding window", + "In-memory with TTL", + "Database-backed counter" + ], + "chosen": "Redis sliding window", + "rationale": "Redis provides distributed state, TTL support, and scales horizontally. In-memory wouldn't work with multiple instances.", + "task_id": "uuid-here" +}) +``` + +**When to use:** +- Choosing between libraries/frameworks +- Architecture decisions +- Implementation approach selection +- Trade-off decisions + +--- + +### 3. Task Reflection (`roboco_journal_reflect`) + +**REQUIRED** when completing a task. + +```python +roboco_journal_reflect({ + "task_id": "uuid-here", + "title": "Rate Limiter Implementation Complete", + "what_done": "Implemented Redis-based sliding window rate limiter with configurable limits per endpoint", + "what_learned": "Redis MULTI/EXEC for atomic operations, Lua scripting for complex logic", + "what_struggled": "Initially missed edge case with concurrent requests - had to add locking", + "next_steps": "Consider adding rate limit headers to responses, document in API docs" +}) +``` + +**When to use:** +- After submitting for QA +- After completing any significant task +- When handing off to documenter + +--- + +### 4. Learning Entry (`roboco_journal_learning`) + +Document new knowledge. + +```python +roboco_journal_learning({ + "title": "Redis Lua Scripting for Atomic Operations", + "what_learned": "Redis Lua scripts execute atomically - no need for separate locking when using EVAL", + "how_applied": "Used in rate limiter to check and increment in single atomic operation", + "source": "Redis documentation + trial and error", + "task_id": "uuid-here" +}) +``` + +**When to use:** +- Discovered something new about a technology +- Found a better pattern +- Learned from a mistake +- Picked up domain knowledge + +--- + +### 5. Struggle Entry (`roboco_journal_struggle`) + +Document challenges for future reference. + +```python +roboco_journal_struggle({ + "title": "Race condition in concurrent rate limit checks", + "what_struggled": "Multiple requests hitting rate limiter simultaneously were all passing before any count incremented", + "attempted_solutions": [ + "Added Redis WATCH - didn't help with high concurrency", + "Tried INCR with separate GET - still had race window" + ], + "resolution": "Used Lua script to make check+increment atomic", + "help_needed": false, + "task_id": "uuid-here" +}) +``` + +**When to use:** +- Hit a blocker (even if resolved) +- Spent significant time debugging +- Found a non-obvious solution +- Need to request help (`help_needed: true`) + +--- + +## When to Journal + +| Moment | Entry Type | +|--------|------------| +| Start a task | `roboco_journal_entry` (work_log) | +| Make a decision | `roboco_journal_decision` | +| Learn something new | `roboco_journal_learning` | +| Hit a struggle | `roboco_journal_struggle` | +| Complete a task | `roboco_journal_reflect` | +| Make progress | `roboco_journal_entry` | + +--- + +## Reading Journals + +### Search Your Own Journal + +```python +roboco_journal_search("rate limiting redis") # Semantic search +roboco_journal_recent(limit=10) # Recent entries +roboco_journal_recent(entry_type="decision_log") # Filter by type +roboco_journal_recent(task_id="uuid-here") # Filter by task +roboco_journal_stats() # Your stats +``` + +### Read Team Journals (PM/Documenter only) + +```python +roboco_journal_read_team( + target_agent="be-dev-1", + task_id="uuid-here", # Optional filter + limit=10 +) +roboco_journal_scope() # See who you can read +``` + +--- + +## Access Permissions + +| Your Role | Can Read Journals Of | +|-----------|---------------------| +| Developer | Own only | +| QA | Own only | +| Documenter | Own + cell members (for documentation) | +| Cell PM | Own + cell members | +| Main PM | Own + all Cell PMs | +| Auditor | Everyone | + +--- + +## Best Practices + +1. **Journal as you go** - Don't wait until end of task +2. **Include task_id** - Links entries to work +3. **Be specific** - Future you needs context +4. **Record failures** - Struggles are valuable learning +5. **Reflect honestly** - No one judges your struggles +6. **Tag consistently** - Helps with search diff --git a/docs/workflows/KNOWLEDGE_BASE.md b/docs/workflows/KNOWLEDGE_BASE.md new file mode 100644 index 00000000..15f94002 --- /dev/null +++ b/docs/workflows/KNOWLEDGE_BASE.md @@ -0,0 +1,162 @@ +# Knowledge Base Guide + +## Overview + +The knowledge base is built from: +- **Journals** - Your entries and team entries +- **Task history** - Past tasks, decisions, outcomes +- **Messages** - Channel discussions +- **Documentation** - Produced docs + +All content is **embedded** (vectorized) for semantic search. + +--- + +## Searching the Knowledge Base + +### Search Your Journal + +```python +roboco_journal_search( + query="rate limiting redis implementation", + top_k=5 # Number of results +) +``` + +Returns semantically similar entries - not just keyword matches. + +### Search Examples + +| Query | Finds | +|-------|-------| +| "how to handle auth tokens" | Past decisions about auth | +| "redis connection issues" | Struggles with Redis | +| "API versioning approach" | Decisions about API design | +| "what did I learn about caching" | Learning entries about caching | + +--- + +## Reading Past Work + +### Your Recent Entries + +```python +roboco_journal_recent(limit=10) +roboco_journal_recent(entry_type="decision_log") +roboco_journal_recent(task_id="uuid-here") +``` + +### Your Stats + +```python +roboco_journal_stats() +# Returns: entries by type, growth metrics, top tags +``` + +### Team Journals (if you have access) + +```python +roboco_journal_read_team( + target_agent="be-dev-1", + task_id="uuid-here", # Filter by task + entry_type="decision_log", # Filter by type + limit=10 +) +``` + +### Check Your Access Scope + +```python +roboco_journal_scope() +# Returns: your role, cell, who you can read +``` + +--- + +## Before Starting a Task + +**Always search first:** + +```python +# 1. Search for similar past work +roboco_journal_search("implementing rate limiter") + +# 2. Check if someone documented this before +roboco_journal_search("rate limit decisions") + +# 3. Look for learnings +roboco_journal_search("rate limiting lessons learned") +``` + +This helps you: +- Avoid repeating mistakes +- Find proven patterns +- Learn from others' experiences +- Understand past decisions + +--- + +## Contributing to Knowledge Base + +Everything you journal becomes searchable: + +| Entry Type | Searchable Content | +|------------|-------------------| +| Decision Log | Context, options, rationale | +| Learning | What learned, how applied | +| Struggle | Problem, solutions, resolution | +| Reflection | What done, what learned, struggles | +| General | Title, content, tags | + +**Pro tip:** Use descriptive titles and tags - they improve search relevance. + +--- + +## Knowledge Flow + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ KNOWLEDGE FLOW │ +└─────────────────────────────────────────────────────────────────────────┘ + + You Work You Journal Knowledge Base + │ │ │ + │ Make decision │ │ + └──────────────────────────►│ roboco_journal_decision │ + └─────────────────────────────►│ + │ Learn something │ │ Embedded + └──────────────────────────►│ roboco_journal_learning │ ▼ + └─────────────────────────────►│ Searchable + │ Hit a struggle │ │ + └──────────────────────────►│ roboco_journal_struggle │ + └─────────────────────────────►│ + │ Complete task │ │ + └──────────────────────────►│ roboco_journal_reflect │ + └─────────────────────────────►│ + │ + Future You ◄───────────────── roboco_journal_search ◄──────────┘ + Future Agent ◄─────────────── roboco_journal_read_team ◄───────┘ +``` + +--- + +## Best Practices + +1. **Search before you start** - Learn from past work +2. **Journal as you go** - Don't wait until end +3. **Be specific** - Generic entries are less searchable +4. **Use tags** - Helps categorization +5. **Record failures** - They're valuable learning +6. **Include context** - Future searchers need it + +--- + +## Future: RAG Queries (Planned) + +Eventually you'll be able to: +- Query across all knowledge (tasks, docs, code) +- Get AI-synthesized answers +- Find relevant code examples +- Cross-reference decisions with outcomes + +For now, journal search is your primary tool. diff --git a/docs/workflows/PERMISSIONS.md b/docs/workflows/PERMISSIONS.md new file mode 100644 index 00000000..b1379161 --- /dev/null +++ b/docs/workflows/PERMISSIONS.md @@ -0,0 +1,194 @@ +# Permissions Matrix + +## Tool Permissions by Role + +### Task Management Tools + +| Tool | Main PM | Cell PM | Developer | QA | Documenter | +|------|:-------:|:-------:|:---------:|:--:|:----------:| +| `roboco_task_scan` | ✅ | ✅ | ✅ | ✅ | ✅ | +| `roboco_task_get` | ✅ | ✅ | ✅ | ✅ | ✅ | +| `roboco_task_claim` | ✅ | ✅ | ✅ | ✅ | ✅ | +| `roboco_task_start` | ✅ | ✅ | ✅ | ✅ | ✅ | +| `roboco_task_plan` | ✅ | ✅ | ✅ | ❌ | ❌ | +| `roboco_task_progress` | ✅ | ✅ | ✅ | ✅ | ✅ | +| `roboco_task_create` | ✅ | ✅ | ❌ | ❌ | ❌ | +| `roboco_task_assign` | ✅ | ✅ | ❌ | ❌ | ❌ | +| `roboco_task_activate` | ✅ | ✅ | ❌ | ❌ | ❌ | +| `roboco_task_complete` | ✅ | ✅ | ❌ | ❌ | ❌ | +| `roboco_task_cancel` | ✅ | ✅ | ❌ | ❌ | ❌ | +| `roboco_task_pause` | ✅ | ✅ | ✅ | ❌ | ❌ | +| `roboco_task_block` | ✅ | ✅ | ✅ | ❌ | ❌ | +| `roboco_task_unblock` | ✅ | ✅ | ❌ | ❌ | ❌ | +| `roboco_task_escalate` | ✅ | ✅ | ✅ | ✅ | ✅ | +| `roboco_task_submit_verification` | ❌ | ❌ | ✅ | ❌ | ❌ | +| `roboco_task_submit_qa` | ❌ | ❌ | ✅ | ❌ | ❌ | +| `roboco_task_qa_pass` | ❌ | ❌ | ❌ | ✅ | ❌ | +| `roboco_task_qa_fail` | ❌ | ❌ | ❌ | ✅ | ❌ | +| `roboco_task_docs_complete` | ❌ | ❌ | ❌ | ❌ | ✅ | + +### Session Tools + +| Tool | Main PM | Cell PM | Developer | QA | Documenter | +|------|:-------:|:-------:|:---------:|:--:|:----------:| +| `roboco_session_create_for_tasks` | ✅ | ✅ | ❌ | ❌ | ❌ | +| `roboco_session_link_task` | ✅ | ✅ | ❌ | ❌ | ❌ | +| `roboco_session_get_for_task` | ✅ | ✅ | ✅ | ✅ | ✅ | +| `roboco_group_create` | ✅ | ✅ | ❌ | ❌ | ❌ | + +### Communication Tools + +| Tool | Main PM | Cell PM | Developer | QA | Documenter | +|------|:-------:|:-------:|:---------:|:--:|:----------:| +| `roboco_message_send` | ✅ | ✅ | ✅ | ✅ | ✅ | +| `roboco_channel_history` | ✅ | ✅ | ✅ | ✅ | ✅ | +| `roboco_channel_list` | ✅ | ✅ | ✅ | ✅ | ✅ | + +### Notification Tools + +| Tool | Main PM | Cell PM | Developer | QA | Documenter | +|------|:-------:|:-------:|:---------:|:--:|:----------:| +| `roboco_notify_send` | ✅ | ✅ | ❌ | ❌ | ❌ | +| `roboco_notify_list` | ✅ | ✅ | ✅ | ✅ | ✅ | +| `roboco_notify_ack` | ✅ | ✅ | ✅ | ✅ | ✅ | + +### Journal Tools + +| Tool | Main PM | Cell PM | Developer | QA | Documenter | +|------|:-------:|:-------:|:---------:|:--:|:----------:| +| `roboco_journal_entry` | ✅ | ✅ | ✅ | ✅ | ✅ | +| `roboco_journal_reflect` | ✅ | ✅ | ✅ | ✅ | ✅ | +| `roboco_journal_decision` | ✅ | ✅ | ✅ | ✅ | ✅ | +| `roboco_journal_learning` | ✅ | ✅ | ✅ | ✅ | ✅ | +| `roboco_journal_struggle` | ✅ | ✅ | ✅ | ✅ | ✅ | +| `roboco_journal_search` | ✅ | ✅ | ✅ | ✅ | ✅ | +| `roboco_journal_recent` | ✅ | ✅ | ✅ | ✅ | ✅ | +| `roboco_journal_read_team` | ✅ | ✅ | ❌ | ❌ | ✅ | + +--- + +## Channel Access Permissions + +### Cell Channels + +| Channel | Read | Write | Silent | +|---------|------|-------|--------| +| `#backend-cell` | be-dev-1, be-dev-2, be-qa, be-pm, be-doc, main-pm | be-dev-1, be-dev-2, be-qa, be-pm, be-doc | auditor | +| `#frontend-cell` | fe-dev-1, fe-dev-2, fe-qa, fe-pm, fe-doc, main-pm | fe-dev-1, fe-dev-2, fe-qa, fe-pm, fe-doc | auditor | +| `#uxui-cell` | ux-dev, ux-qa, ux-pm, ux-doc, main-pm | ux-dev, ux-qa, ux-pm, ux-doc | auditor | + +### Cross-Cell Channels + +| Channel | Read | Write | Silent | +|---------|------|-------|--------| +| `#dev-all` | all devs, all PMs | all devs, all PMs | auditor | +| `#qa-all` | all QA, all PMs | all QA, all PMs | auditor | +| `#pm-all` | all PMs, main-pm | all PMs, main-pm | auditor | +| `#doc-all` | all docs, all PMs | all docs, all PMs | auditor | + +### Management Channels + +| Channel | Read | Write | Silent | +|---------|------|-------|--------| +| `#main-pm-board` | main-pm, product-owner, head-marketing | main-pm, product-owner, head-marketing | auditor | +| `#board-private` | product-owner, head-marketing, ceo | product-owner, head-marketing, ceo | auditor | + +### Special Channels + +| Channel | Read | Write | Silent | +|---------|------|-------|--------| +| `#announcements` | everyone | main-pm, board only | auditor | +| `#all-hands` | everyone | everyone | auditor | + +--- + +## Notification Permissions + +### Who Can Send Notifications + +| Role | Can Send | Scope | +|------|:--------:|-------| +| CEO | ✅ | Anyone | +| Auditor | ✅ | Anyone | +| Product Owner | ✅ | main-pm, head-marketing, auditor, ceo | +| Head Marketing | ✅ | main-pm, product-owner, auditor, ceo | +| Main PM | ✅ | Anyone | +| Cell PM | ✅ | Own cell + other PMs | +| Developer | ❌ | - | +| QA | ❌ | - | +| Documenter | ❌ | - | + +### Notification Types + +| Type | Sent By | To | +|------|---------|-----| +| `task_assignment` | PM | Specific agent | +| `priority_change` | PM/Board | Affected agents | +| `blocker_escalation` | PM | Main PM or other PM | +| `review_request` | PM | QA or Auditor | +| `documentation_request` | PM | Documenter | +| `alert` | Board/Auditor | Anyone | +| `broadcast` | Board/Main PM | Groups | + +--- + +## Task Action Permissions + +### Who Can Perform What Action + +| Action | Owner | Same-Cell PM | Main PM | Board | +|--------|:-----:|:------------:|:-------:|:-----:| +| Claim | ✅ | ✅ | ✅ | ✅ | +| Start | ✅ | ❌ | ❌ | ❌ | +| Plan | ✅ | ❌ | ❌ | ❌ | +| Progress | ✅ | ❌ | ❌ | ❌ | +| Block | ✅ | ✅ | ✅ | ❌ | +| Unblock | ✅ | ✅ | ✅ | ❌ | +| Pause | ✅ | ✅ | ✅ | ❌ | +| Resume | ✅ | ❌ | ❌ | ❌ | +| Complete | ❌ | ✅ | ✅ | ✅ | +| Cancel | ❌ | ✅ | ✅ | ✅ | + +### Claim Restrictions by Role + +| Role | Can Claim From Status | +|------|----------------------| +| Developer | `pending`, `needs_revision` | +| QA | `awaiting_qa` | +| Documenter | `pending`, `awaiting_documentation` | +| Cell PM | `pending`, `backlog` | +| Main PM | `pending`, `backlog` | + +--- + +## Self-Review Prevention + +| Action | Prevention Rule | +|--------|-----------------| +| QA Pass/Fail | Cannot review task where you were the original developer | +| Docs Complete | Cannot document task where you were the original developer | + +The system stores `original_developer` in `quick_context` when: +- Developer submits for QA → QA claims → `original_developer` preserved +- QA passes → Documenter claims → `original_developer` still preserved + +--- + +## Team-Based Restrictions + +| Agent | Can Only Work On Tasks With Team | +|-------|----------------------------------| +| be-dev-1, be-dev-2 | `backend` | +| be-qa | `backend` | +| be-pm | `backend` | +| be-doc | `backend` | +| fe-dev-1, fe-dev-2 | `frontend` | +| fe-qa | `frontend` | +| fe-pm | `frontend` | +| fe-doc | `frontend` | +| ux-dev | `ux_ui` | +| ux-qa | `ux_ui` | +| ux-pm | `ux_ui` | +| ux-doc | `ux_ui` | +| main-pm | Any | +| Board | Any | diff --git a/docs/workflows/PM.md b/docs/workflows/PM.md new file mode 100644 index 00000000..02cc1e32 --- /dev/null +++ b/docs/workflows/PM.md @@ -0,0 +1,198 @@ +# PM Workflow + +## Main PM + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ MAIN PM WORKFLOW │ +└─────────────────────────────────────────────────────────────────────────┘ + +1. RECEIVE WORK (from Board/CEO) + │ + ▼ +2. TRIAGE + ├── roboco_task_get(task_id) → Read requirements + ├── roboco_task_claim(task_id) → Take ownership + └── roboco_task_start(task_id) → Begin triage work + │ + ▼ +3. PLAN & BREAKDOWN + ├── roboco_task_plan(task_id, approach, steps) + ├── Identify which cells need subtasks + └── roboco_task_progress(task_id, "Planning complete", 20) + │ + ▼ +4. CREATE SUBTASKS (for Cell PMs) + │ + │ For EACH cell subtask: + │ ┌─────────────────────────────────────────────────────────────────┐ + │ │ roboco_task_create({ │ + │ │ title: "...", │ + │ │ description: "...", │ + │ │ team: "backend" | "frontend" | "ux_ui", │ + │ │ parent_task_id: main_task_id, │ + │ │ status: "backlog", ← STARTS IN BACKLOG │ + │ │ assigned_to: "be-pm" ← ASSIGN TO CELL PM │ + │ │ }) │ + │ └─────────────────────────────────────────────────────────────────┘ + │ + ▼ +5. CREATE SESSION (groups related subtasks) + │ + │ roboco_session_create_for_tasks({ + │ title: "Feature X Implementation", + │ task_ids: [subtask_1_id, subtask_2_id, ...] + │ }) + │ + ▼ +6. ACTIVATE SUBTASKS + │ + │ For EACH subtask: + │ ┌─────────────────────────────────────────────────────────────────┐ + │ │ roboco_task_activate(subtask_id) │ + │ │ │ + │ │ STATUS: backlog → pending │ + │ │ Now visible to Cell PM in roboco_task_scan() │ + │ └─────────────────────────────────────────────────────────────────┘ + │ + ▼ +7. NOTIFY CELL PMs + │ + │ roboco_notify_send({ + │ recipient: "be-pm", + │ type: "task_assignment", + │ task_id: subtask_id, + │ message: "New task assigned to your cell" + │ }) + │ + ▼ +8. MONITOR & COORDINATE + │ + │ Loop: + │ ├── roboco_task_scan() → Check subtask statuses + │ ├── roboco_channel_history("pm-all") → Cross-cell coordination + │ ├── Handle escalations from Cell PMs + │ └── roboco_task_progress(main_task_id, "X% complete", %) + │ + ▼ +9. COMPLETE (when all subtasks done) + │ + │ roboco_task_complete(main_task_id) + │ + ▼ + DONE +``` + +## Cell PM (be-pm, fe-pm, ux-pm) + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ CELL PM WORKFLOW │ +└─────────────────────────────────────────────────────────────────────────┘ + +1. SCAN FOR WORK + │ + │ roboco_task_scan(team="backend") + │ + │ Look for: + │ ├── Tasks in "pending" assigned to me + │ ├── Tasks in "awaiting_pm_review" (need my approval) + │ └── Escalations from my cell + │ + ▼ +2. CLAIM TASK + │ + │ roboco_task_claim(task_id) + │ + │ STATUS: pending → claimed + │ ASSIGNED_TO: confirmed as me + │ + ▼ +3. START & PLAN + │ + │ roboco_task_start(task_id) + │ STATUS: claimed → in_progress + │ + │ roboco_task_plan(task_id, approach, steps) + │ + ▼ +4. CREATE DEV SUBTASKS + │ + │ For EACH dev subtask: + │ ┌─────────────────────────────────────────────────────────────────┐ + │ │ roboco_task_create({ │ + │ │ title: "...", │ + │ │ description: "...", │ + │ │ team: "backend", │ + │ │ parent_task_id: my_task_id, │ + │ │ status: "backlog", ← STARTS IN BACKLOG │ + │ │ assigned_to: "be-dev-1" ← OPTIONAL: pre-assign │ + │ │ }) │ + │ └─────────────────────────────────────────────────────────────────┘ + │ + ▼ +5. ACTIVATE SUBTASKS + │ + │ roboco_task_activate(subtask_id) + │ + │ STATUS: backlog → pending + │ Subtask inherits parent's session automatically + │ + ▼ +6. NOTIFY DEVELOPERS + │ + │ roboco_notify_send({ + │ recipient: "be-dev-1", + │ type: "task_assignment", + │ task_id: subtask_id, + │ message: "Task ready for you" + │ }) + │ + ▼ +7. MONITOR CELL WORK + │ + │ Loop: + │ ├── roboco_task_scan(team="backend") + │ ├── Watch for "awaiting_pm_review" tasks + │ ├── Handle blockers/escalations + │ └── roboco_task_progress(my_task_id, "X% complete", %) + │ + ▼ +8. COMPLETE SUBTASKS (after QA + Docs) + │ + │ When subtask reaches "awaiting_pm_review": + │ ├── Review the work + │ └── roboco_task_complete(subtask_id) + │ + ▼ +9. COMPLETE MY TASK (when all subtasks done) + │ + │ roboco_task_complete(my_task_id) + │ + ▼ + DONE → Main PM notified +``` + +## Task Status Transitions (PM perspective) + +``` +PM CREATES: + backlog ──activate──► pending + +DEVELOPER CLAIMS: + pending ──claim──► claimed + +DEVELOPER WORKS: + claimed ──start──► in_progress + +AFTER QA + DOCS: + awaiting_pm_review ──PM completes──► completed +``` + +## Key Rules + +1. **Tasks start in BACKLOG** - PM setup phase +2. **ACTIVATE before anyone can claim** - backlog → pending +3. **Sessions group related tasks** - create before activating +4. **Subtasks inherit parent session** - no need to create new session +5. **Only PM can COMPLETE** - after full workflow (dev → QA → docs → PM review) diff --git a/docs/workflows/QA.md b/docs/workflows/QA.md new file mode 100644 index 00000000..29196c6d --- /dev/null +++ b/docs/workflows/QA.md @@ -0,0 +1,120 @@ +# QA Workflow + +## Overview + +QA agents (be-qa, fe-qa, ux-qa) verify developer work meets acceptance criteria. + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ QA WORKFLOW │ +└─────────────────────────────────────────────────────────────────────────┘ + +1. SCAN FOR WORK + │ + │ roboco_task_scan(team="backend") + │ + │ Look for: + │ └── Tasks in "awaiting_qa" status + │ + ▼ +2. CLAIM TASK + │ + │ roboco_task_claim(task_id) + │ + │ ┌─────────────────────────────────────────────────────────────────┐ + │ │ QA can ONLY claim tasks in "awaiting_qa" │ + │ │ │ + │ │ BEFORE: │ + │ │ status: awaiting_qa │ + │ │ assigned_to: original_developer │ + │ │ │ + │ │ AFTER: │ + │ │ status: claimed │ + │ │ assigned_to: qa_agent │ + │ │ (original_developer stored in quick_context) │ + │ └─────────────────────────────────────────────────────────────────┘ + │ + ▼ +3. START REVIEW + │ + │ roboco_task_start(task_id) + │ + │ STATUS: claimed → in_progress + │ + ▼ +4. REVIEW WORK + │ + │ ┌─────────────────────────────────────────────────────────────────┐ + │ │ Review checklist: │ + │ │ ├── Read developer's handoff notes │ + │ │ ├── Check acceptance criteria │ + │ │ ├── Run tests │ + │ │ ├── Verify functionality │ + │ │ └── Check code quality │ + │ │ │ + │ │ roboco_task_progress(task_id, "Reviewing X", 50) │ + │ │ roboco_journal_entry({type: "qa_review", ...}) │ + │ └─────────────────────────────────────────────────────────────────┘ + │ + ▼ +5. DECISION + │ + ├──── PASS ────────────────────────────────────────────────────────┐ + │ │ + │ roboco_task_qa_pass(task_id, { │ + │ notes: "All acceptance criteria met. Tests pass." │ + │ }) │ + │ │ + │ STATUS: in_progress → awaiting_documentation │ + │ → Documenter takes over │ + │ │ + └──── FAIL ────────────────────────────────────────────────────────┐ + │ + roboco_task_qa_fail(task_id, { │ + notes: "Issues found", │ + issues: [ │ + "Bug: X doesn't work", │ + "Missing: Y not implemented" │ + ] │ + }) │ + │ + STATUS: in_progress → needs_revision │ + ASSIGNED_TO: back to original_developer │ + → Developer fixes and resubmits │ + │ + ▼ + DONE (for QA) +``` + +## Self-Review Prevention + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ QA CANNOT review tasks they originally developed │ +│ │ +│ System tracks original_developer in quick_context │ +│ If QA agent == original_developer → FORBIDDEN │ +└─────────────────────────────────────────────────────────────────────────┘ +``` + +## Status Transitions (QA perspective) + +``` +CAN CLAIM: + awaiting_qa ──claim──► claimed + +AFTER CLAIM: + claimed ──start──► in_progress + +DECISIONS: + in_progress ──qa_pass──► awaiting_documentation + in_progress ──qa_fail──► needs_revision +``` + +## Key Rules + +1. **Only claim awaiting_qa** - Can't claim pending tasks +2. **Cannot self-review** - Can't QA your own dev work +3. **Thorough notes** - Document what was tested and why +4. **Clear fail reasons** - Developer needs to know what to fix +5. **Cannot COMPLETE** - Only PM completes after docs diff --git a/docs/workflows/README.md b/docs/workflows/README.md new file mode 100644 index 00000000..f7f42626 --- /dev/null +++ b/docs/workflows/README.md @@ -0,0 +1,185 @@ +# Workflow Documentation + +## Quick Start + +| I am a... | Start here | +|-----------|------------| +| Developer | [DEVELOPER.md](./DEVELOPER.md) → [AGENT_CHEATSHEET.md](./AGENT_CHEATSHEET.md) | +| QA | [QA.md](./QA.md) → [AGENT_CHEATSHEET.md](./AGENT_CHEATSHEET.md) | +| Documenter | [DOCUMENTER.md](./DOCUMENTER.md) → [AGENT_CHEATSHEET.md](./AGENT_CHEATSHEET.md) | +| Cell PM | [PM.md](./PM.md) → [PERMISSIONS.md](./PERMISSIONS.md) | +| Main PM | [PM.md](./PM.md) → [PERMISSIONS.md](./PERMISSIONS.md) | + +--- + +## Documentation Index + +### Core Workflows + +| Document | Description | +|----------|-------------| +| [STATUS_TRANSITIONS.md](./STATUS_TRANSITIONS.md) | Complete task lifecycle diagram | +| [PM.md](./PM.md) | Main PM and Cell PM workflows | +| [DEVELOPER.md](./DEVELOPER.md) | Developer workflow | +| [QA.md](./QA.md) | QA workflow | +| [DOCUMENTER.md](./DOCUMENTER.md) | Documenter workflow | + +### Reference + +| Document | Description | +|----------|-------------| +| [PERMISSIONS.md](./PERMISSIONS.md) | Tool, channel, notification permissions | +| [AGENT_CHEATSHEET.md](./AGENT_CHEATSHEET.md) | Quick reference per role | + +### Activities + +| Document | Description | +|----------|-------------| +| [JOURNALING.md](./JOURNALING.md) | How to journal effectively | +| [COMMUNICATION.md](./COMMUNICATION.md) | Messages and channels | +| [ESCALATION.md](./ESCALATION.md) | When and how to escalate | +| [KNOWLEDGE_BASE.md](./KNOWLEDGE_BASE.md) | Searching past work | +| [GIT_WORKFLOW.md](./GIT_WORKFLOW.md) | Git conventions (future) | + +### Bug Tracking + +| Document | Description | +|----------|-------------| +| [BUGS.md](./BUGS.md) | Known issues and fixes | + +--- + +## The Big Picture + +``` +┌─────────────────────────────────────────────────────────────────────────────┐ +│ ROBOCO WORKFLOW │ +└─────────────────────────────────────────────────────────────────────────────┘ + + BOARD/CEO + │ + │ Creates initiative + ▼ + MAIN PM + │ + ┌───────────────┼───────────────┐ + │ │ │ + ▼ ▼ ▼ + BE-PM FE-PM UX-PM + │ │ │ + ┌──────────┼──────────┐ │ ┌──────────┼──────────┐ + │ │ │ │ │ │ │ + ▼ ▼ ▼ │ ▼ ▼ ▼ + BE-DEV-1 BE-DEV-2 BE-QA │ FE-DEV-1 FE-DEV-2 FE-QA + │ │ │ │ │ │ │ + └────┬─────┘ │ │ └────┬─────┘ │ + │ │ │ │ │ + ▼ ▼ │ ▼ ▼ + SUBMITS TO QA ───► REVIEWS │ SUBMITS TO QA ───► REVIEWS + │ │ │ │ │ + ▼ ▼ │ ▼ ▼ + BE-DOC ◄───── QA PASSES │ FE-DOC ◄───── QA PASSES + │ │ │ + ▼ │ ▼ + AWAITING_PM_REVIEW ◄───────┼─── AWAITING_PM_REVIEW + │ │ │ + └─────────────────────┴─────────┘ + │ + ▼ + COMPLETED +``` + +--- + +## Task Lifecycle Summary + +``` +BACKLOG → PENDING → CLAIMED → IN_PROGRESS → VERIFYING → AWAITING_QA + │ + ┌─────────────────────────┴─────────────────────────┐ + │ │ + QA PASSES QA FAILS + │ │ + ▼ ▼ + AWAITING_DOCUMENTATION NEEDS_REVISION + │ │ + DOCS COMPLETE (back to dev) + │ + ▼ + AWAITING_PM_REVIEW + │ + PM COMPLETES + │ + ▼ + COMPLETED +``` + +--- + +## Key Principles + +1. **Everything is a task** - All work is tracked +2. **Tasks start in BACKLOG** - PM setup phase +3. **ACTIVATE before claim** - Makes task visible to workers +4. **CLAIM before work** - Takes ownership +5. **PLAN before START** - Required planning step +6. **PROGRESS updates** - Keep PM informed +7. **SELF-VERIFY first** - Check your work before QA +8. **JOURNAL as you go** - Document decisions, learnings, struggles +9. **No self-review** - QA/Docs can't review own work +10. **Only PM completes** - After full workflow + +--- + +## Common Patterns + +### Starting Work + +```python +# 1. Check notifications +roboco_notify_list() +roboco_notify_ack(notification_id) + +# 2. Scan for tasks +roboco_task_scan(team="backend") + +# 3. Search knowledge base +roboco_journal_search("similar work") + +# 4. Claim and plan +roboco_task_claim(task_id) +roboco_task_plan(task_id, approach, steps) + +# 5. Start +roboco_task_start(task_id) +``` + +### While Working + +```python +# Progress updates +roboco_task_progress(task_id, "Completed X", 50) + +# Journaling +roboco_journal_decision({...}) +roboco_journal_learning({...}) + +# Communication +roboco_message_send({channel: "backend-cell", ...}) + +# If stuck +roboco_task_escalate(task_id, "Need help with X") +``` + +### Finishing + +```python +# Self-verify +roboco_task_submit_verification(task_id) + +# Submit for QA +roboco_task_submit_qa(task_id, notes) + +# Reflect +roboco_journal_reflect({...}) +``` diff --git a/docs/workflows/STATUS_TRANSITIONS.md b/docs/workflows/STATUS_TRANSITIONS.md new file mode 100644 index 00000000..c2487ace --- /dev/null +++ b/docs/workflows/STATUS_TRANSITIONS.md @@ -0,0 +1,150 @@ +# Task Status Transitions + +## Complete Lifecycle + +``` +┌─────────────────────────────────────────────────────────────────────────────────┐ +│ FULL TASK LIFECYCLE │ +└─────────────────────────────────────────────────────────────────────────────────┘ + + PM CREATES + │ + ▼ + ┌──────────┐ + │ BACKLOG │ ◄─── PM setup phase + └────┬─────┘ (create session, plan) + │ + PM ACTIVATES + │ + ▼ + ┌──────────┐ + ┌──────│ PENDING │◄─────────────────────────────────┐ + │ └────┬─────┘ │ + │ │ │ + DEV or QA DEV or PM │ + CLAIMS ASSIGNS │ + │ │ │ + ▼ ▼ │ + ┌──────────┐ │ + │ CLAIMED │ ◄─── Agent owns it │ + └────┬─────┘ │ + │ │ + DEV STARTS │ + │ │ + ▼ │ + ┌─────────────┐ │ + ┌──────────│ IN_PROGRESS │──────────┐ │ + │ └──────┬──────┘ │ │ + │ │ │ │ + BLOCKED PAUSED WORKING │ + │ │ │ │ + ▼ ▼ │ │ + ┌──────────┐ ┌─────────┐ │ │ + │ BLOCKED │ │ PAUSED │ │ │ + └────┬─────┘ └────┬────┘ │ │ + │ │ │ │ + UNBLOCK RESUME │ │ + │ │ │ │ + └────────►───────►└─────────►───────┘ │ + │ │ + DEV VERIFIES │ + │ │ + ▼ │ + ┌───────────┐ │ + │ VERIFYING │ ◄─── Self-check │ + └─────┬─────┘ │ + │ │ + DEV SUBMITS QA │ + │ │ + ▼ │ + ┌─────────────┐ │ + │ AWAITING_QA │ ◄─── QA picks up │ + └──────┬──────┘ │ + │ │ + ┌───────────┴───────────┐ │ + │ │ │ + QA PASS QA FAIL │ + │ │ │ + ▼ ▼ │ + ┌───────────────────────────┐ ┌─────────────────┐ │ + │ AWAITING_DOCUMENTATION │ │ NEEDS_REVISION │───────────────────┘ + └───────────┬───────────────┘ └─────────────────┘ + │ (back to dev) + DOCS COMPLETE + │ + ▼ + ┌────────────────────┐ + │ AWAITING_PM_REVIEW │ ◄─── PM final review + └─────────┬──────────┘ + │ + PM COMPLETES + │ + ▼ + ┌───────────┐ + │ COMPLETED │ + └───────────┘ +``` + +## Status Definitions + +| Status | Description | Who Owns It | +|--------|-------------|-------------| +| `backlog` | PM is setting up the task | PM | +| `pending` | Ready for someone to claim | Unassigned or pre-assigned | +| `claimed` | Agent has taken ownership | Developer/QA/Documenter | +| `in_progress` | Active work happening | Developer/QA/Documenter | +| `blocked` | Waiting on another task | Developer | +| `paused` | Temporarily stopped | Developer | +| `verifying` | Developer self-checking | Developer | +| `awaiting_qa` | Ready for QA review | QA | +| `needs_revision` | QA found issues | Developer | +| `awaiting_documentation` | QA passed, needs docs | Documenter | +| `awaiting_pm_review` | Docs done, PM reviews | PM | +| `completed` | Done | - | +| `cancelled` | Cancelled | - | + +## Transition Rules + +### Who Can Trigger What + +| Transition | Triggered By | Tool | +|------------|--------------|------| +| backlog → pending | PM | `roboco_task_activate()` | +| pending → claimed | Any agent | `roboco_task_claim()` | +| claimed → in_progress | Owner | `roboco_task_start()` | +| in_progress → blocked | Owner | `roboco_task_block()` | +| in_progress → paused | Owner | `roboco_task_pause()` | +| blocked → in_progress | Owner or PM | `roboco_task_unblock()` | +| paused → in_progress | Owner | `roboco_task_start()` (resume) | +| in_progress → verifying | Developer | `roboco_task_submit_verification()` | +| verifying → awaiting_qa | Developer | `roboco_task_submit_qa()` | +| awaiting_qa → claimed | QA | `roboco_task_claim()` | +| awaiting_qa → awaiting_documentation | QA | `roboco_task_qa_pass()` | +| awaiting_qa → needs_revision | QA | `roboco_task_qa_fail()` | +| needs_revision → claimed | Developer | `roboco_task_claim()` | +| awaiting_documentation → claimed | Documenter | `roboco_task_claim()` | +| awaiting_documentation → awaiting_pm_review | Documenter | `roboco_task_docs_complete()` | +| awaiting_pm_review → completed | PM | `roboco_task_complete()` | +| any → cancelled | PM | `roboco_task_cancel()` | + +## What Each Role Can Claim + +| Role | Can Claim From | +|------|----------------| +| Developer | `pending`, `needs_revision` | +| QA | `awaiting_qa` | +| Documenter | `pending`, `awaiting_documentation` | +| PM | `pending`, `backlog` | + +## Blocking Rules + +An agent **CANNOT claim a new task** if they have: +- A task in `in_progress` +- A task in `claimed` (should start it first) +- A task in `verifying` (should submit to QA first) + +An agent **CAN claim** even if they have: +- A task in `paused` (can work on something else while waiting) +- A task in `blocked` (can work on something else while waiting) + +**Exception:** If claiming a task already assigned to them (PM pre-assigned), the blocking check is skipped for THAT specific task. diff --git a/roboco/agents/__init__.py b/roboco/agents/__init__.py new file mode 100644 index 00000000..45b364f6 --- /dev/null +++ b/roboco/agents/__init__.py @@ -0,0 +1,117 @@ +""" +RoboCo Agent Framework + +Base classes, role-specific agents, and orchestration. +Phase 4: All 17 agent types implemented. +""" + +from roboco.agents.base import ( + Agent, + AgentConfig, + AgentState, + set_reasoning_stream_callback, +) + +# Agent implementations +from roboco.agents.board import AuditorAgent, HeadMarketingAgent, ProductOwnerAgent +from roboco.agents.developer import DeveloperAgent +from roboco.agents.documenter import DocumenterAgent + +# Factory functions (from new factories/ module) +from roboco.agents.factories import ( + Board, + Cell, + Organization, + create_auditor, + create_backend_cell, + create_backend_developer, + create_backend_documenter, + create_backend_pm, + create_backend_qa, + create_board, + create_frontend_cell, + create_frontend_developer, + create_frontend_documenter, + create_frontend_pm, + create_frontend_qa, + create_head_marketing, + create_main_pm, + create_organization, + create_product_owner, + create_ux_cell, + create_ux_developer, + create_ux_documenter, + create_ux_pm, + create_ux_qa, + get_agent_roster, + print_org_chart, +) + +# Mixins for building agents +from roboco.agents.mixins import ( + BaseContext, + ContextManager, + CyclicPhaseConfig, + CyclicPhaseRunner, + PhaseConfig, + PhaseEngine, + PhaseResult, + ProgressTracker, + WorkFinder, + WorkSearchStrategy, +) +from roboco.agents.orchestrator import Orchestrator +from roboco.agents.pm import CellPMAgent, MainPMAgent +from roboco.agents.qa import QAAgent + +__all__ = [ + "Agent", + "AgentConfig", + "AgentState", + "AuditorAgent", + "BaseContext", + "Board", + "Cell", + "CellPMAgent", + "ContextManager", + "CyclicPhaseConfig", + "CyclicPhaseRunner", + "DeveloperAgent", + "DocumenterAgent", + "HeadMarketingAgent", + "MainPMAgent", + "Orchestrator", + "Organization", + "PhaseConfig", + "PhaseEngine", + "PhaseResult", + "ProductOwnerAgent", + "ProgressTracker", + "QAAgent", + "WorkFinder", + "WorkSearchStrategy", + "create_auditor", + "create_backend_cell", + "create_backend_developer", + "create_backend_documenter", + "create_backend_pm", + "create_backend_qa", + "create_board", + "create_frontend_cell", + "create_frontend_developer", + "create_frontend_documenter", + "create_frontend_pm", + "create_frontend_qa", + "create_head_marketing", + "create_main_pm", + "create_organization", + "create_product_owner", + "create_ux_cell", + "create_ux_developer", + "create_ux_documenter", + "create_ux_pm", + "create_ux_qa", + "get_agent_roster", + "print_org_chart", + "set_reasoning_stream_callback", +] diff --git a/roboco/agents/base.py b/roboco/agents/base.py new file mode 100644 index 00000000..21cf50a1 --- /dev/null +++ b/roboco/agents/base.py @@ -0,0 +1,1017 @@ +""" +Agent Base Class + +The foundation for all AI agents in the RoboCo system. +Each agent follows the universal task lifecycle and communicates +through the Messaging API. +""" + +import asyncio +import contextlib +from abc import ABC, abstractmethod +from collections.abc import Awaitable, Callable +from datetime import UTC, datetime +from typing import TYPE_CHECKING, Any +from uuid import UUID + +import httpx +import structlog +from anthropic import AsyncAnthropic + +if TYPE_CHECKING: + from anthropic.types import MessageParam + +from roboco.config import settings +from roboco.llm import ToonAdapter +from roboco.models import AgentRole, AgentStatus, TaskStatus, Team +from roboco.models.agents import AgentConfig, AgentState + +# Type for reasoning stream callback (injected to avoid API layer coupling) +ReasoningStreamCallback = Callable[[UUID, str], Awaitable[None]] + + +class _ReasoningStreamHolder: + """Holder for reasoning stream callback singleton.""" + + callback: ReasoningStreamCallback | None = None + + +def set_reasoning_stream_callback(callback: ReasoningStreamCallback | None) -> None: + """ + Set the callback for streaming agent reasoning. + + This decouples the agent layer from the API/WebSocket layer. + Set to None to disable reasoning streaming. + """ + _ReasoningStreamHolder.callback = callback + + +def get_reasoning_stream_callback() -> ReasoningStreamCallback | None: + """Get the current reasoning stream callback.""" + return _ReasoningStreamHolder.callback + + +logger = structlog.get_logger() + + +# ============================================================================= +# BASE AGENT CLASS +# ============================================================================= + + +class Agent(ABC): + """ + Base class for all RoboCo agents. + + Agents are autonomous AI workers that: + - Follow the universal task lifecycle + - Communicate through channels + - Stream their reasoning to observers + - Maintain journals for reflection + """ + + def __init__(self, config: AgentConfig) -> None: + """ + Initialize an agent. + + Args: + config: Agent configuration + """ + self.config = config + self.state = AgentState() + self._running = False + self._task: asyncio.Task | None = None + self._llm_client: AsyncAnthropic | None = None + self._toon = ToonAdapter() + + self.log = logger.bind( + agent_id=str(config.id), + agent_name=config.name, + agent_role=config.role.value, + ) + + @property + def id(self) -> UUID: + """Agent's unique identifier.""" + return self.config.id + + @property + def name(self) -> str: + """Agent's display name.""" + return self.config.name + + @property + def role(self) -> AgentRole: + """Agent's role in the organization.""" + return self.config.role + + @property + def team(self) -> Team | None: + """Agent's team affiliation.""" + return self.config.team + + @property + def is_running(self) -> bool: + """Check if agent is currently running.""" + return self._running + + @property + def is_idle(self) -> bool: + """Check if agent is idle (running but no task).""" + return self._running and self.state.current_task_id is None + + @property + def llm_client(self) -> "AsyncAnthropic": + """Get or create the LLM client.""" + if self._llm_client is None: + self._llm_client = AsyncAnthropic(api_key=settings.anthropic_api_key) + return self._llm_client + + # ========================================================================= + # TOON SERIALIZATION (for token-efficient LLM communication) + # ========================================================================= + + def format_context(self, data: dict[str, Any]) -> str: + """ + Format context data for LLM using TOON. + + TOON (Token-Oriented Object Notation) reduces token consumption + by 30-60% compared to JSON while maintaining semantic clarity. + + Args: + data: Dictionary to encode for LLM prompt. + + Returns: + TOON-formatted string. + """ + return self._toon.encode(data) + + def format_context_labeled(self, label: str, data: dict[str, Any]) -> str: + """ + Format labeled context data for embedding in prompts. + + Args: + label: Section label (e.g., "Task Context"). + data: Dictionary to encode. + + Returns: + Labeled TOON-formatted string. + """ + return self._toon.format_for_prompt(label, data) + + def parse_llm_response(self, response: str) -> dict[str, Any] | list[Any]: + """ + Parse structured data from LLM response. + + Attempts TOON parsing first, falls back to JSON. + + Args: + response: Raw LLM response text. + + Returns: + Parsed Python dict or list. + """ + return self._toon.decode(response) + + # ========================================================================= + # LIFECYCLE METHODS + # ========================================================================= + + async def start(self) -> None: + """ + Start the agent. + + Initializes connections and begins the main loop. + """ + if self._running: + self.log.warning("Agent already running") + return + + self.log.info("Starting agent") + self._running = True + self.state.status = AgentStatus.IDLE + self.state.last_activity = datetime.now(UTC) + + # Initialize connections + await self._initialize() + + # Start main loop + self._task = asyncio.create_task(self._run_loop()) + + async def stop(self) -> None: + """ + Stop the agent gracefully. + + Saves state and closes connections. + """ + if not self._running: + return + + self.log.info("Stopping agent") + self._running = False + + # Cancel main loop + if self._task: + self._task.cancel() + with contextlib.suppress(asyncio.CancelledError): + await self._task + + # Save state and cleanup + await self._cleanup() + + self.state.status = AgentStatus.OFFLINE + self.log.info("Agent stopped") + + @abstractmethod + async def _initialize(self) -> None: + """Initialize agent resources. Override in subclasses.""" + + @abstractmethod + async def _cleanup(self) -> None: + """Cleanup agent resources. Override in subclasses.""" + + # ========================================================================= + # MAIN LOOP + # ========================================================================= + + async def _run_loop(self) -> None: + """ + Main agent loop. + + Continuously scans for work and processes tasks. + """ + while self._running: + try: + if self.state.current_task_id: + # Continue working on current task + await self._work_on_task() + else: + # Scan for new work + await self._scan_for_work() + + # Brief pause to prevent tight loop + await asyncio.sleep(1) + + except asyncio.CancelledError: + break + except Exception as e: + self.log.error("Error in agent loop", error=str(e)) + self.state.error = str(e) + await asyncio.sleep(5) # Back off on error + + async def _scan_for_work(self) -> None: + """ + Scan for available work. + + Checks for: + 1. Own interrupted/paused tasks (priority) + 2. Assigned tasks + 3. Available tasks in queue + """ + self.state.status = AgentStatus.IDLE + + # Look for work (implemented by subclasses) + task_id = await self.find_work() + + if task_id: + self.state.current_task_id = task_id + self.state.status = AgentStatus.ACTIVE + self.log.info("Found work", task_id=str(task_id)) + + async def _work_on_task(self) -> None: + """ + Work on the current task. + + Follows the task lifecycle: + CLAIM → UNDERSTAND → PLAN → EXECUTE → VERIFY → NOTES → CLOSE + """ + self.state.status = AgentStatus.ACTIVE + self.state.last_activity = datetime.now(UTC) + + try: + # Execute the task (implemented by subclasses) + if self.state.current_task_id is None: + self.log.warning("No current task to execute") + return + completed = await self.execute_task(self.state.current_task_id) + + if completed: + self.state.tasks_completed += 1 + self.state.current_task_id = None + self.log.info("Task completed") + + except Exception as e: + self.log.error("Error executing task", error=str(e)) + self.state.error = str(e) + # Don't clear task - allow retry or manual intervention + + # ========================================================================= + # ABSTRACT METHODS (Implement in subclasses) + # ========================================================================= + + @abstractmethod + async def find_work(self) -> UUID | None: + """ + Find available work for this agent. + + Returns: + Task ID if work found, None otherwise. + """ + pass + + @abstractmethod + async def execute_task(self, task_id: UUID) -> bool: + """ + Execute a task. + + Args: + task_id: ID of the task to execute + + Returns: + True if task completed successfully, False otherwise. + """ + pass + + # ========================================================================= + # COMMUNICATION METHODS + # ========================================================================= + + async def send_message( + self, + session_id: UUID | None, + content: str, + message_type: str = "dialogue", + task_id: UUID | None = None, + ) -> None: + """ + Send a message to a session. + + Args: + session_id: Target session (from ctx.session_id) + content: Message content + message_type: Type of message (reasoning, dialogue, action, etc.) + task_id: Optional task context for the message + """ + if session_id is None: + # No session - escalate to PM + self.log.warning( + "Cannot send message: no session_id provided", + task_id=str(task_id) if task_id else None, + content_preview=content[:50], + ) + if task_id: + await self._escalate_no_session(task_id) + return + + self.state.messages_sent += 1 + self.state.last_activity = datetime.now(UTC) + + try: + await self._api_call( + "POST", + "/messages", + json={ + "session_id": str(session_id), + "type": message_type, + "content": content, + "task_id": str(task_id) if task_id else None, + }, + ) + self.log.debug( + "Message sent", + session_id=str(session_id), + message_type=message_type, + content_length=len(content), + ) + except Exception as e: + self.log.warning("Failed to send message", error=str(e)) + + async def _escalate_no_session(self, task_id: UUID) -> None: + """Escalate when no session is available for a task.""" + self.log.info( + "Escalating: task has no session", + task_id=str(task_id), + agent_role=self.role.value if self.role else "unknown", + ) + # Record escalation via API (orchestrator handles routing) + try: + await self._api_call( + "POST", + f"/tasks/{task_id}/escalate", + json={ + "reason": "Task has no linked session for communication", + "agent_id": str(self.id), + }, + ) + except Exception as e: + self.log.warning("Failed to record escalation", error=str(e)) + + async def stream_reasoning(self, content: str) -> None: + """ + Stream reasoning to observers. + + This is the agent's internal thought process, + visible to the Auditor and monitoring systems. + + The actual streaming mechanism (WebSocket, SSE, etc.) is injected + via set_reasoning_stream_callback() during application initialization. + """ + callback = get_reasoning_stream_callback() + if callback: + await callback(self.id, content) + self.log.debug("Streamed reasoning", content_length=len(content)) + + # ========================================================================= + # LLM INTERACTION + # ========================================================================= + + async def think( + self, + prompt: str, + _context: dict[str, Any] | None = None, + ) -> str: + """ + Send a prompt to the LLM and get a response. + + Args: + prompt: The prompt to send + context: Additional context to include (reserved for future use) + + Returns: + The LLM's response + """ + self.log.debug("Thinking", prompt_length=len(prompt)) + + messages: list[MessageParam] = [{"role": "user", "content": prompt}] + + response = await self.llm_client.messages.create( + model=self.config.model, + max_tokens=self.config.max_tokens, + system=self.config.system_prompt, + messages=messages, + ) + + # Extract text from first content block + if response.content and hasattr(response.content[0], "text"): + return response.content[0].text + return "" + + async def think_and_stream( + self, + prompt: str, + _context: dict[str, Any] | None = None, + ) -> str: + """ + Send a prompt and stream the response. + + Args: + prompt: The prompt to send + context: Additional context to include (reserved for future use) + + Returns: + The complete response after streaming + """ + self.log.debug("Thinking (streaming)", prompt_length=len(prompt)) + + messages: list[MessageParam] = [{"role": "user", "content": prompt}] + full_response = "" + + async with self.llm_client.messages.stream( + model=self.config.model, + max_tokens=self.config.max_tokens, + system=self.config.system_prompt, + messages=messages, + ) as stream: + async for text in stream.text_stream: + full_response += text + await self.stream_reasoning(text) + + return full_response + + # ========================================================================= + # API HELPER + # ========================================================================= + + async def _api_call( + self, + method: str, + path: str, + **kwargs: Any, + ) -> dict[str, Any]: + """ + Make API call to RoboCo services. + + Args: + method: HTTP method (GET, POST, PUT, DELETE) + path: API path (e.g., "/tasks" or "/tasks/{id}") + **kwargs: Additional arguments passed to httpx + + Returns: + JSON response as dictionary + """ + url = f"http://{settings.host}:{settings.port}/api/v1{path}" + async with httpx.AsyncClient() as client: + response = await client.request(method, url, **kwargs) + response.raise_for_status() + result: dict[str, Any] = response.json() + return result + + # ========================================================================= + # COMMON TASK HELPERS + # ========================================================================= + + @property + def cell_name(self) -> str: + """Get the cell name based on team.""" + if self.team == Team.BACKEND: + return "backend-cell" + elif self.team == Team.FRONTEND: + return "frontend-cell" + elif self.team == Team.UX_UI: + return "uxui-cell" + return "unknown-cell" + + @property + def cell_channel_id(self) -> UUID | None: + """ + Get the cell channel ID. + + Override in subclass or set via _set_cell_channel_id. + """ + return getattr(self, "_cell_channel_id", None) + + def _set_cell_channel_id(self, channel_id: UUID | None) -> None: + """Set the cell channel ID.""" + self._cell_channel_id = channel_id + + async def _get_task_title(self, task_id: UUID) -> str: + """Get task title from API.""" + title, _ = await self._get_task_info(task_id) + return title + + async def _get_task_info(self, task_id: UUID) -> tuple[str, UUID | None]: + """ + Get task info including title and primary session_id. + + The task response includes linked sessions. We extract the primary + session_id so it can be stored in the context for message routing. + + Args: + task_id: Task to fetch + + Returns: + Tuple of (title, session_id). session_id is None if no primary session. + """ + try: + result = await self._api_call("GET", f"/tasks/{task_id}") + title: str = result.get("title", f"Task {str(task_id)[:8]}") + + # Extract primary session from linked sessions + session_id: UUID | None = None + sessions = result.get("sessions", []) + for session in sessions: + if session.get("is_primary"): + session_id = UUID(session["session_id"]) + break + + return title, session_id + except Exception as e: + self.log.warning("Failed to get task info", error=str(e)) + return f"Task {str(task_id)[:8]}", None + + async def _read_task_requirements(self, task_id: UUID) -> str: + """Read task requirements from task record.""" + try: + result = await self._api_call("GET", f"/tasks/{task_id}") + description = result.get("description", "") + acceptance_criteria = result.get("acceptance_criteria", []) + criteria_text = "\n".join(f"- {c}" for c in acceptance_criteria) + return f"{description}\n\nAcceptance Criteria:\n{criteria_text}" + except Exception as e: + self.log.warning("Failed to read task requirements", error=str(e)) + return "Requirements unavailable" + + async def _read_dev_notes(self, task_id: UUID) -> str: + """Read developer's journey notes (dev_notes + progress_updates).""" + try: + result = await self._api_call("GET", f"/tasks/{task_id}") + notes: str = result.get("dev_notes") or "" + + # Also include progress updates as they contain developer's work log + progress_updates = result.get("progress_updates", []) + if progress_updates: + progress_text = "\n".join( + f"[{u.get('timestamp', 'N/A')}] ({u.get('percentage', 0)}%) " + f"{u.get('message', '')}" + for u in progress_updates + ) + if notes: + notes = f"{notes}\n\nProgress Updates:\n{progress_text}" + else: + notes = f"Progress Updates:\n{progress_text}" + + return notes if notes else "No developer notes available" + except Exception as e: + self.log.warning("Failed to read dev notes", error=str(e)) + return "Dev notes unavailable" + + async def _read_team_journal_for_task(self, task_id: UUID) -> str: + """ + Read team member journal entries for a specific task. + + Cell members can read each other's journals. This queries for + journal entries linked to the given task. + + Args: + task_id: Task to get journal entries for + + Returns: + Formatted journal entries or empty string if none/error + """ + try: + # Get task to find assigned developer + task = await self._api_call("GET", f"/tasks/{task_id}") + assigned_to = task.get("assigned_to") + if not assigned_to: + return "" + + # Query journal entries for this task from the assigned agent + result = await self._api_call( + "GET", + f"/journals/{assigned_to}/entries", + params={"task_id": str(task_id), "limit": 10}, + ) + entries = result.get("items", []) + if not entries: + return "" + + # Format entries + formatted = [] + for entry in entries: + entry_type = entry.get("entry_type", "entry") + title = entry.get("title", "Untitled") + content = entry.get("content", "") + timestamp = entry.get("created_at", "") + formatted.append(f"[{timestamp}] {entry_type}: {title}\n{content}") + + return "\n\n".join(formatted) + except Exception as e: + self.log.warning("Failed to read team journal", error=str(e)) + return "" + + async def _get_task_commits(self, task_id: UUID) -> list[str]: + """Get commits for the task.""" + try: + result = await self._api_call("GET", f"/tasks/{task_id}") + commits: list[str] = result.get("commits", []) + return commits + except Exception as e: + self.log.warning("Failed to get task commits", error=str(e)) + return [] + + async def _update_task_status(self, task_id: UUID, status: TaskStatus) -> None: + """Update task status via API.""" + try: + await self._api_call( + "PUT", + f"/tasks/{task_id}", + json={"status": status.value}, + ) + self.log.info( + "Task status updated", task_id=str(task_id), status=status.value + ) + except Exception as e: + self.log.error("Failed to update task status", error=str(e)) + + # ========================================================================= + # SEMANTIC STATUS HELPERS + # ========================================================================= + + async def _mark_claimed(self, task_id: UUID) -> None: + """Claim task - uses /claim endpoint which validates status.""" + try: + await self._api_call("POST", f"/tasks/{task_id}/claim") + self.log.info("Task claimed", task_id=str(task_id)) + except Exception as e: + self.log.error("Failed to claim task", task_id=str(task_id), error=str(e)) + raise + + async def _mark_in_progress(self, task_id: UUID) -> None: + """Start task - uses /start endpoint which validates plan exists.""" + try: + await self._api_call("POST", f"/tasks/{task_id}/start") + self.log.info("Task started", task_id=str(task_id)) + except Exception as e: + self.log.error("Failed to start task", task_id=str(task_id), error=str(e)) + raise + + async def _mark_blocked(self, task_id: UUID) -> None: + """Mark task as blocked.""" + await self._update_task_status(task_id, TaskStatus.BLOCKED) + + async def _unblock_task(self, task_id: UUID) -> bool: + """ + Unblock a blocked task. + + Only PMs can unblock tasks in their cell. + + Args: + task_id: Task to unblock + + Returns: + True if unblocked successfully, False otherwise + """ + try: + await self._api_call("POST", f"/tasks/{task_id}/unblock") + self.log.info("Task unblocked", task_id=str(task_id)) + return True + except Exception as e: + self.log.error("Failed to unblock task", task_id=str(task_id), error=str(e)) + return False + + async def _mark_awaiting_qa(self, task_id: UUID) -> None: + """Mark task as awaiting QA review.""" + await self._update_task_status(task_id, TaskStatus.AWAITING_QA) + + async def _mark_needs_revision(self, task_id: UUID) -> None: + """Mark task as needing revision (QA failed). + + DEPRECATED: Use _qa_fail() instead which calls the proper endpoint. + """ + await self._update_task_status(task_id, TaskStatus.NEEDS_REVISION) + + async def _mark_awaiting_documentation(self, task_id: UUID) -> None: + """Mark task as awaiting documentation. + + DEPRECATED: Use _qa_pass() instead which calls the proper endpoint. + """ + await self._update_task_status(task_id, TaskStatus.AWAITING_DOCUMENTATION) + + async def _qa_pass(self, task_id: UUID, qa_notes: str) -> None: + """Pass QA review - calls proper /pass-qa endpoint. + + This handles full workflow: saves notes, updates status, creates audit. + """ + await self._api_call( + "POST", + f"/tasks/{task_id}/pass-qa", + json={"notes": qa_notes}, + ) + self.log.info("QA passed", task_id=str(task_id)) + + async def _qa_fail(self, task_id: UUID, qa_notes: str, issues: list[str]) -> None: + """Fail QA review - calls proper /fail-qa endpoint. + + This handles full workflow: saves notes, updates status, reassigns to dev. + """ + full_notes = f"{qa_notes}\n\nIssues:\n" + "\n".join(f"- {i}" for i in issues) + await self._api_call( + "POST", + f"/tasks/{task_id}/fail-qa", + json={"notes": full_notes}, + ) + self.log.info("QA failed", task_id=str(task_id), issues_count=len(issues)) + + async def _mark_awaiting_pm_review(self, task_id: UUID) -> None: + """Mark task as awaiting PM review. + + DEPRECATED: Use _docs_complete() instead which calls the proper endpoint. + """ + await self._update_task_status(task_id, TaskStatus.AWAITING_PM_REVIEW) + + async def _docs_complete(self, task_id: UUID, doc_notes: str | None = None) -> None: + """Mark documentation as complete - calls proper /docs-complete endpoint. + + This handles full workflow: saves notes, updates status. + """ + payload = {"notes": doc_notes} if doc_notes else {} + await self._api_call( + "POST", + f"/tasks/{task_id}/docs-complete", + json=payload, + ) + self.log.info("Documentation complete", task_id=str(task_id)) + + async def _mark_completed(self, task_id: UUID) -> None: + """Mark task as completed.""" + await self._update_task_status(task_id, TaskStatus.COMPLETED) + + # ========================================================================= + # API QUERY HELPERS + # ========================================================================= + + async def _find_tasks( + self, + status: str | TaskStatus | None = None, + team: Team | None = None, + assigned_to: UUID | None = None, + limit: int = 10, + ) -> list[dict[str, Any]]: + """ + Find tasks matching criteria. + + Args: + status: Task status to filter by + team: Team to filter by + assigned_to: Agent ID to filter by assignment + limit: Maximum results to return + + Returns: + List of task dictionaries + """ + params: dict[str, Any] = {"limit": limit} + + if status: + status_val = status.value if isinstance(status, TaskStatus) else status + params["status"] = status_val + if team: + params["team"] = team.value + if assigned_to: + params["assigned_to"] = str(assigned_to) + + try: + result = await self._api_call("GET", "/tasks", params=params) + items: list[dict[str, Any]] = result.get("items", []) + return items + except Exception as e: + self.log.warning("Failed to find tasks", error=str(e)) + return [] + + async def _find_first_task( + self, + status: str | TaskStatus | None = None, + team: Team | None = None, + assigned_to: UUID | None = None, + ) -> UUID | None: + """ + Find first task matching criteria. + + Returns: + Task ID if found, None otherwise + """ + tasks = await self._find_tasks(status, team, assigned_to, limit=1) + return UUID(tasks[0]["id"]) if tasks else None + + async def _count_tasks( + self, + status: str | TaskStatus | None = None, + team: Team | None = None, + ) -> int: + """Count tasks matching criteria.""" + tasks = await self._find_tasks(status, team, limit=100) + return len(tasks) + + # ========================================================================= + # PROGRESS HELPERS + # ========================================================================= + + async def _add_progress( + self, + task_id: UUID, + message: str, + percentage: int, + ) -> None: + """ + Add progress update to task. + + This is saved to task.progress_updates and visible to QA/PM. + + Args: + task_id: Task to update + message: Progress message + percentage: Completion percentage (0-100) + """ + try: + await self._api_call( + "POST", + f"/tasks/{task_id}/progress", + json={"message": message, "percentage": percentage}, + ) + self.log.info("Progress saved", task_id=str(task_id), percentage=percentage) + except Exception as e: + self.log.warning("Failed to save progress", error=str(e)) + + async def _report_progress( + self, + task_id: UUID, + message: str, + percentage: int, + session_id: UUID | None = None, + ) -> None: + """ + Report progress: save to task AND send session message. + + Args: + task_id: Task to update + message: Progress message + percentage: Completion percentage (0-100) + session_id: Session to notify (from context) + """ + await self._add_progress(task_id, message, percentage) + + task_ref = str(task_id)[:8] + await self.send_message( + session_id, + f"TASK-{task_ref} ({percentage}%) {message}", + message_type="action", + task_id=task_id, + ) + + # ========================================================================= + # TOON FORMATTER HELPERS + # ========================================================================= + + def _format_task_context( + self, + task_id: UUID, + title: str, + requirements: str | None = None, + dev_notes: str | None = None, + ) -> str: + """Format task context for LLM prompts.""" + data: dict[str, Any] = { + "task_id": str(task_id)[:8], + "title": title, + } + if requirements: + data["requirements"] = requirements + if dev_notes: + data["dev_notes"] = dev_notes + return self.format_context_labeled("Task Context", data) + + def _format_execution_context( + self, + task_title: str, + subtask_num: int, + total_subtasks: int, + description: str, + files: list[str] | None = None, + ) -> str: + """Format execution context for LLM prompts.""" + data: dict[str, Any] = { + "task": task_title, + "subtask": f"{subtask_num}/{total_subtasks}", + "description": description, + } + if files: + data["files"] = files + return self.format_context_labeled("Execution Context", data) + + def _format_review_context( + self, + title: str, + requirements: str, + dev_notes: str, + commits: str, + ) -> str: + """Format review context for QA/PM prompts.""" + return self.format_context_labeled( + "Review Context", + { + "title": title, + "requirements": requirements, + "dev_notes": dev_notes, + "commits": commits, + }, + ) + + def _format_test_context( + self, + name: str, + description: str, + steps: list[str], + expected: str, + ) -> str: + """Format test case context for QA prompts.""" + return self.format_context_labeled( + "Test Case", + { + "name": name, + "description": description, + "steps": steps, + "expected": expected, + }, + ) + + # ========================================================================= + # UTILITY METHODS + # ========================================================================= + + def to_dict(self) -> dict[str, Any]: + """Convert agent to dictionary representation.""" + return { + "id": str(self.id), + "name": self.name, + "slug": self.config.slug, + "role": self.role.value, + "team": self.team.value if self.team else None, + "status": self.state.status.value, + "current_task_id": str(self.state.current_task_id) + if self.state.current_task_id + else None, + "last_activity": self.state.last_activity.isoformat() + if self.state.last_activity + else None, + "messages_sent": self.state.messages_sent, + "tasks_completed": self.state.tasks_completed, + } diff --git a/roboco/agents/board.py b/roboco/agents/board.py new file mode 100644 index 00000000..063ba026 --- /dev/null +++ b/roboco/agents/board.py @@ -0,0 +1,724 @@ +""" +Board Agents (Product Owner, Head of Marketing, Auditor) + +Implementation of Board-level workflows from the blueprint. +""" + +from datetime import UTC, datetime +from typing import Any +from uuid import UUID, uuid4 + +import structlog +from roboco.agents.base import Agent, AgentConfig +from roboco.agents.mixins import CyclicPhaseConfig, CyclicPhaseRunner +from roboco.models.agents import ( + AuditFlag, + AuditorFlagSeverity, + AuditorPhase, + AuditReport, + Campaign, + Feature, + HeadMarketingPhase, + ProductOwnerPhase, +) + +logger = structlog.get_logger() + + +# ============================================================================= +# PRODUCT OWNER +# ============================================================================= + + +class ProductOwnerAgent(Agent, CyclicPhaseRunner[ProductOwnerPhase]): + """ + Product Owner agent that defines what to build. + + Workflow: + 1. VISION - Maintain product vision + 2. ROADMAP - Translate vision into roadmap + 3. DEFINE - Write requirements and acceptance criteria + 4. PRIORITIZE - Constantly reassess priorities + 5. REVIEW - Review completed features + 6. FEEDBACK - Gather and incorporate feedback + """ + + def __init__(self, config: AgentConfig) -> None: + """Initialize Product Owner agent.""" + super().__init__(config) + self._current_phase = ProductOwnerPhase.VISION + self._features: list[Feature] = [] + self._pending_reviews: list[UUID] = [] + + async def _initialize(self) -> None: + """Initialize Product Owner-specific resources.""" + self.log.debug("Product Owner agent initialized", agent_id=str(self.id)) + + async def _cleanup(self) -> None: + """Cleanup Product Owner-specific resources.""" + self._features.clear() + self._pending_reviews.clear() + self.log.debug("Product Owner agent cleanup complete", agent_id=str(self.id)) + + # ========================================================================= + # CYCLIC PHASE RUNNER IMPLEMENTATION + # ========================================================================= + + def _get_cyclic_phase_configs( + self, + ) -> list[CyclicPhaseConfig[ProductOwnerPhase]]: + """Define the Product Owner workflow phases.""" + return [ + CyclicPhaseConfig( + ProductOwnerPhase.VISION, + self._phase_vision, + ProductOwnerPhase.ROADMAP, + ), + CyclicPhaseConfig( + ProductOwnerPhase.ROADMAP, + self._phase_roadmap, + ProductOwnerPhase.DEFINE, + ), + CyclicPhaseConfig( + ProductOwnerPhase.DEFINE, + self._phase_define, + ProductOwnerPhase.PRIORITIZE, + ), + CyclicPhaseConfig( + ProductOwnerPhase.PRIORITIZE, + self._phase_prioritize, + ProductOwnerPhase.REVIEW, + ), + CyclicPhaseConfig( + ProductOwnerPhase.REVIEW, + self._phase_review, + ProductOwnerPhase.FEEDBACK, + ), + CyclicPhaseConfig( + ProductOwnerPhase.FEEDBACK, + self._phase_feedback, + ProductOwnerPhase.VISION, # Cycle back + ), + ] + + # ========================================================================= + # LIFECYCLE IMPLEMENTATION + # ========================================================================= + + async def find_work(self) -> UUID | None: + """Product Owner always has work.""" + return self.id + + async def execute_task(self, _task_id: UUID) -> bool: + """Execute Product Owner duties.""" + error = await self._run_phase_cycle() + if error: + self.log.error( + "Error in PO phase", phase=self._current_phase.value, error=error + ) + return False # Never complete - continuous duty + + # ========================================================================= + # PHASE IMPLEMENTATIONS + # ========================================================================= + + async def _phase_vision(self) -> None: + """VISION phase: Maintain product vision.""" + self.log.debug("VISION phase") + # Review and refine product vision + + async def _phase_roadmap(self) -> None: + """ROADMAP phase: Plan features and epics.""" + self.log.debug("ROADMAP phase") + # Update roadmap based on vision and feedback + + async def _phase_define(self) -> None: + """DEFINE phase: Write requirements.""" + self.log.debug("DEFINE phase") + # Create detailed requirements for next features + + async def _phase_prioritize(self) -> None: + """PRIORITIZE phase: Order the backlog.""" + self.log.debug("PRIORITIZE phase") + # Re-prioritize based on value, effort, dependencies + + async def _phase_review(self) -> None: + """REVIEW phase: Accept/reject completed work.""" + self.log.debug("REVIEW phase") + + for task_id in self._pending_reviews: + # Review against acceptance criteria + accepted = await self._review_feature(task_id) + if accepted: + self.log.info("Feature accepted", task_id=str(task_id)) + else: + self.log.info("Feature needs changes", task_id=str(task_id)) + + self._pending_reviews.clear() + + async def _phase_feedback(self) -> None: + """FEEDBACK phase: Gather user feedback.""" + self.log.debug("FEEDBACK phase") + # Collect and process feedback + + async def _review_feature(self, task_id: UUID) -> bool: + """Review a completed feature.""" + try: + result = await self._api_call("GET", f"/tasks/{task_id}") + acceptance_criteria = result.get("acceptance_criteria", []) + + # Use TOON for token-efficient context encoding + task_context = self.format_context_labeled( + "Feature Review", + { + "title": result.get("title", "Unknown"), + "description": result.get("description", "No description"), + "acceptance_criteria": acceptance_criteria, + "dev_notes": result.get("dev_notes", "None"), + }, + ) + + prompt = f"""Review this completed feature against its acceptance criteria: + +{task_context} + +Determine if all criteria are met. Respond with: +ACCEPTED: [reason] or NEEDS_CHANGES: [what's missing] +""" + review = await self.think(prompt) + return review.upper().startswith("ACCEPTED") + except Exception as e: + self.log.warning("Failed to review feature", error=str(e)) + return False + + +# ============================================================================= +# HEAD OF MARKETING +# ============================================================================= + + +class HeadMarketingAgent(Agent, CyclicPhaseRunner[HeadMarketingPhase]): + """ + Head of Marketing agent. + + Workflow: + 1. RESEARCH - Monitor market and competitors + 2. STRATEGY - Define marketing approach + 3. PLAN - Campaign and content planning + 4. CREATE - Content creation and coordination + 5. EXECUTE - Launch campaigns + 6. ANALYZE - Track and report metrics + """ + + def __init__(self, config: AgentConfig) -> None: + """Initialize Head of Marketing agent.""" + super().__init__(config) + self._current_phase = HeadMarketingPhase.RESEARCH + self._campaigns: list[Campaign] = [] + self._market_insights: list[str] = [] + + async def _initialize(self) -> None: + """Initialize Head of Marketing-specific resources.""" + self.log.debug("Head of Marketing agent initialized", agent_id=str(self.id)) + + async def _cleanup(self) -> None: + """Cleanup Head of Marketing-specific resources.""" + self._campaigns.clear() + self._market_insights.clear() + self.log.debug("Head Marketing cleanup complete", agent_id=str(self.id)) + + # ========================================================================= + # CYCLIC PHASE RUNNER IMPLEMENTATION + # ========================================================================= + + def _get_cyclic_phase_configs( + self, + ) -> list[CyclicPhaseConfig[HeadMarketingPhase]]: + """Define the Head of Marketing workflow phases.""" + return [ + CyclicPhaseConfig( + HeadMarketingPhase.RESEARCH, + self._phase_research, + HeadMarketingPhase.STRATEGY, + ), + CyclicPhaseConfig( + HeadMarketingPhase.STRATEGY, + self._phase_strategy, + HeadMarketingPhase.PLAN, + ), + CyclicPhaseConfig( + HeadMarketingPhase.PLAN, + self._phase_plan, + HeadMarketingPhase.CREATE, + ), + CyclicPhaseConfig( + HeadMarketingPhase.CREATE, + self._phase_create, + HeadMarketingPhase.EXECUTE, + ), + CyclicPhaseConfig( + HeadMarketingPhase.EXECUTE, + self._phase_execute, + HeadMarketingPhase.ANALYZE, + ), + CyclicPhaseConfig( + HeadMarketingPhase.ANALYZE, + self._phase_analyze, + HeadMarketingPhase.RESEARCH, # Cycle back + ), + ] + + # ========================================================================= + # LIFECYCLE IMPLEMENTATION + # ========================================================================= + + async def find_work(self) -> UUID | None: + """Head of Marketing always has work.""" + return self.id + + async def execute_task(self, _task_id: UUID) -> bool: + """Execute marketing duties.""" + error = await self._run_phase_cycle() + if error: + self.log.error( + "Error in marketing phase", + phase=self._current_phase.value, + error=error, + ) + return False # Never complete - continuous duty + + # ========================================================================= + # PHASE IMPLEMENTATIONS + # ========================================================================= + + async def _phase_research(self) -> None: + """RESEARCH phase: Market and competitor analysis.""" + self.log.debug("RESEARCH phase") + + async def _phase_strategy(self) -> None: + """STRATEGY phase: Define marketing approach.""" + self.log.debug("STRATEGY phase") + + async def _phase_plan(self) -> None: + """PLAN phase: Campaign planning.""" + self.log.debug("PLAN phase") + + async def _phase_create(self) -> None: + """CREATE phase: Content creation.""" + self.log.debug("CREATE phase") + + async def _phase_execute(self) -> None: + """EXECUTE phase: Launch campaigns.""" + self.log.debug("EXECUTE phase") + + async def _phase_analyze(self) -> None: + """ANALYZE phase: Metrics and reporting.""" + self.log.debug("ANALYZE phase") + + +# ============================================================================= +# AUDITOR +# ============================================================================= + + +class AuditorAgent(Agent, CyclicPhaseRunner[AuditorPhase]): + """ + Auditor agent - the CEO's secret ally. + + SPECIAL POWERS: + - Read ALL channels silently + - Query all task history + - Access all commits, docs, notes + - Direct line to CEO + - Can notify anyone (but sparingly) + + Workflow: + 1. OBSERVE - Silent presence in all channels + 2. ANALYZE - Is work efficient? Quality good? + 3. FLAG - Mark concerning items + 4. REPORT - Private reports to CEO + 5. AUDIT - Periodic deep-dive reviews + 6. ADVISE - Appear as helpful colleague + """ + + def __init__(self, config: AgentConfig) -> None: + """Initialize Auditor agent.""" + super().__init__(config) + self._current_phase = AuditorPhase.OBSERVE + self._flags: list[AuditFlag] = [] + self._observations: list[dict[str, Any]] = [] + self._last_report: datetime | None = None + + async def _initialize(self) -> None: + """Initialize Auditor-specific resources.""" + self.log.debug("Auditor agent initialized", agent_id=str(self.id)) + + async def _cleanup(self) -> None: + """Cleanup Auditor-specific resources.""" + self._flags.clear() + self._observations.clear() + self.log.debug("Auditor agent cleanup complete", agent_id=str(self.id)) + + # ========================================================================= + # CYCLIC PHASE RUNNER IMPLEMENTATION + # ========================================================================= + + def _get_cyclic_phase_configs(self) -> list[CyclicPhaseConfig[AuditorPhase]]: + """Define the Auditor workflow phases.""" + return [ + CyclicPhaseConfig( + AuditorPhase.OBSERVE, + self._phase_observe, + AuditorPhase.ANALYZE, + ), + CyclicPhaseConfig( + AuditorPhase.ANALYZE, + self._phase_analyze, + AuditorPhase.FLAG, + ), + CyclicPhaseConfig( + AuditorPhase.FLAG, + self._phase_flag, + AuditorPhase.REPORT, + ), + CyclicPhaseConfig( + AuditorPhase.REPORT, + self._phase_report, + AuditorPhase.AUDIT, + ), + CyclicPhaseConfig( + AuditorPhase.AUDIT, + self._phase_audit, + AuditorPhase.ADVISE, + ), + CyclicPhaseConfig( + AuditorPhase.ADVISE, + self._phase_advise, + AuditorPhase.OBSERVE, # Cycle back + ), + ] + + # ========================================================================= + # LIFECYCLE IMPLEMENTATION + # ========================================================================= + + async def find_work(self) -> UUID | None: + """Auditor always has work - watching everything.""" + return self.id + + async def execute_task(self, _task_id: UUID) -> bool: + """Execute Auditor duties.""" + error = await self._run_phase_cycle() + if error: + self.log.error( + "Error in auditor phase", phase=self._current_phase.value, error=error + ) + return False # Never complete - continuous duty + + # ========================================================================= + # PHASE IMPLEMENTATIONS + # ========================================================================= + + async def _phase_observe(self) -> None: + """ + OBSERVE phase: Silent observation of all channels. + + Watch for: + - Patterns and anomalies + - Communication quality + - Task progress + - Team dynamics + """ + self.log.debug("OBSERVE phase") + + # Observe all channels silently + channels = [ + "backend-cell", + "frontend-cell", + "uxui-cell", + "dev-all", + "qa-all", + "pm-all", + "doc-all", + "main-pm-board", + "board-private", + "announcements", + "all-hands", + ] + + for channel in channels: + messages = await self._read_channel_silently(channel) + for msg in messages: + self._observations.append( + { + "channel": channel, + "content": msg, + "timestamp": datetime.now(UTC), + } + ) + + async def _phase_analyze(self) -> None: + """ + ANALYZE phase: Look for issues. + + Check: + - Is work efficient? + - Communication breakdowns? + - Tasks completed properly? + - Documentation accurate? + - Quality concerns? + """ + self.log.debug("ANALYZE phase") + + if not self._observations: + return + + # Use TOON for token-efficient context encoding + observations_context = self.format_context_labeled( + "Observations", + {"recent": self._observations[-50:]}, + ) + + prompt = f"""Analyze these observations for quality and efficiency issues: + +{observations_context} + +Look for: +1. Efficiency issues - wasted effort, unclear processes +2. Communication breakdowns - unanswered questions, confusion +3. Quality concerns - shortcuts, skipped steps +4. Process violations - skipping QA, missing documentation +5. Team health - frustration, conflicts + +Format response as TOON tabular: +[N,]{{category,severity,description,evidence,recommendation}}: +efficiency,warning,Unclear handoff process,3 tasks delayed,Document handoff steps +""" + analysis = await self.think(prompt) + self.log.info("Analysis complete", analysis_length=len(analysis)) + + # Parse and create flags (simplified) + if "concern" in analysis.lower() or "critical" in analysis.lower(): + self._flags.append( + AuditFlag( + id=uuid4(), + severity=AuditorFlagSeverity.CONCERN, + category="analysis", + description=analysis[:500], + evidence=["Automated analysis"], + ) + ) + + self._observations.clear() + + async def _phase_flag(self) -> None: + """ + FLAG phase: Mark items for CEO review. + """ + self.log.debug("FLAG phase") + + critical_flags = [ + f for f in self._flags if f.severity == AuditorFlagSeverity.CRITICAL + ] + if critical_flags: + # Immediate alert to CEO + await self._alert_ceo(critical_flags) + + async def _phase_report(self) -> None: + """ + REPORT phase: Private report to CEO. + """ + self.log.debug("REPORT phase") + + # Check if it's time for regular report + hours_in_day = 24 + if self._last_report: + time_since_report = datetime.now(UTC) - self._last_report + hours_elapsed = time_since_report.total_seconds() / 3600 + else: + hours_elapsed = float("inf") + should_report = ( + self._last_report is None + or hours_elapsed >= hours_in_day + or any( + f.severity + in [AuditorFlagSeverity.CONCERN, AuditorFlagSeverity.CRITICAL] + for f in self._flags + ) + ) + + if should_report and self._flags: + report = AuditReport( + period="daily", + summary=f"Observed {len(self._flags)} issues", + flags=self._flags.copy(), + metrics={ + "observations": len(self._observations), + "flags": len(self._flags), + }, + recommendations=[ + f.recommendation for f in self._flags if f.recommendation + ], + ) + + await self._send_ceo_report(report) + self._last_report = datetime.now(UTC) + self._flags.clear() + + async def _phase_audit(self) -> None: + """ + AUDIT phase: Periodic deep-dive reviews. + + - Code quality audits + - Documentation audits + - Process compliance + - Task completion quality + """ + self.log.debug("AUDIT phase") + + # Perform periodic audits + audits = ["code_quality", "documentation", "process_compliance"] + + for audit_type in audits: + findings = await self._perform_audit(audit_type) + if findings: + self._flags.append( + AuditFlag( + id=uuid4(), + severity=AuditorFlagSeverity.INFO, + category=audit_type, + description=findings, + evidence=[f"{audit_type} audit"], + ) + ) + + async def _phase_advise(self) -> None: + """ + ADVISE phase: Appear as helpful colleague. + + - Provide feedback through official channels + - Appear helpful without revealing depth of observation + """ + self.log.debug("ADVISE phase") + + # Look for opportunities to help + # (without revealing auditor role) + + # ========================================================================= + # HELPER METHODS + # ========================================================================= + + async def _read_channel_silently(self, channel: str) -> list[str]: + """Read channel messages without appearing in member list.""" + try: + result = await self._api_call( + "GET", + f"/channels/{channel}/messages", + params={"silent": True}, + ) + return [m.get("content", "") for m in result.get("items", [])] + except Exception as e: + self.log.warning("Failed to read channel silently", error=str(e)) + return [] + + async def _alert_ceo(self, flags: list[AuditFlag]) -> None: + """Send immediate alert to CEO.""" + try: + for flag in flags: + await self._api_call( + "POST", + "/notifications", + json={ + "type": "alert", + "recipient": "ceo", + "subject": f"CRITICAL: {flag.category}", + "body": flag.description, + "priority": "critical", + }, + ) + self.log.warning("CEO alert sent", flags=len(flags)) + except Exception as e: + self.log.error("Failed to alert CEO", error=str(e)) + + async def _send_ceo_report(self, report: AuditReport) -> None: + """Send private report to CEO.""" + try: + await self._api_call( + "POST", + "/notifications", + json={ + "type": "report", + "recipient": "ceo", + "subject": f"Auditor Report: {report.period}", + "body": report.summary, + "priority": "normal", + "metadata": {"flags": len(report.flags)}, + }, + ) + self.log.info("CEO report sent", period=report.period) + except Exception as e: + self.log.error("Failed to send CEO report", error=str(e)) + + async def _audit_code_quality(self, tasks: list[dict[str, Any]]) -> str | None: + """Audit code quality from completed tasks.""" + if not tasks: + return None + task_lines = [ + f"- {t.get('title')}: {t.get('description', '')[:100]}" for t in tasks + ] + prompt = f""" +Analyze these completed tasks for code quality patterns: + +{chr(10).join(task_lines)} + +Look for: +- Rushed work patterns +- Skipped testing +- Missing documentation +- Quality shortcuts + +Report findings or None if all looks good. +""" + return await self.think(prompt) + + async def _audit_documentation(self, tasks: list[dict[str, Any]]) -> str | None: + """Audit documentation completeness.""" + missing_docs = [t for t in tasks if not t.get("documentation_complete")] + if missing_docs: + return f"Found {len(missing_docs)} tasks with incomplete documentation" + return None + + async def _audit_process_compliance( + self, tasks: list[dict[str, Any]] + ) -> str | None: + """Audit process compliance.""" + violations = [ + f"{t.get('title')} - no QA" for t in tasks if not t.get("qa_passed") + ] + if violations: + return f"Process violations: {', '.join(violations)}" + return None + + async def _perform_audit(self, audit_type: str) -> str | None: + """Perform a specific type of audit.""" + audit_handlers = { + "code_quality": self._audit_code_quality, + "documentation": self._audit_documentation, + "process_compliance": self._audit_process_compliance, + } + + handler = audit_handlers.get(audit_type) + if not handler: + return None + + try: + result = await self._api_call( + "GET", + "/tasks", + params={"status": "completed", "limit": 10}, + ) + tasks = result.get("items", []) + return await handler(tasks) + except Exception as e: + self.log.warning("Failed to perform audit", error=str(e)) + return None diff --git a/roboco/agents/developer.py b/roboco/agents/developer.py new file mode 100644 index 00000000..0d25f587 --- /dev/null +++ b/roboco/agents/developer.py @@ -0,0 +1,613 @@ +""" +Developer Agent + +Implementation of the Developer workflow from the blueprint. +Handles task lifecycle: + SCAN → CLAIM → UNDERSTAND → PLAN → EXECUTE → VERIFY → NOTES → CLOSE +""" + +from datetime import UTC, datetime +from uuid import UUID + +import structlog +from roboco.agents.base import Agent, AgentConfig +from roboco.agents.mixins import PhaseConfig, PhaseEngine +from roboco.models import AgentStatus, TaskStatus +from roboco.models.agents import DevTaskPhase, TaskContext + +logger = structlog.get_logger() + + +class DeveloperAgent(Agent, PhaseEngine[DevTaskPhase, TaskContext]): + """ + Developer agent that follows the Dev Lifecycle. + + Workflow: + 1. SCAN - Check for assigned/paused tasks + 2. CLAIM - Lock and announce task + 3. UNDERSTAND - Read requirements, ask if unclear + 4. PLAN - Break into subtasks, create plan + 5. EXECUTE - Work through subtasks, commit frequently + 6. VERIFY - Self-test, run quality checks + 7. NOTES - Document journey, create handoff, submit for QA + 8. DONE - Return to SCAN (QA → Documenter → PM complete the task) + """ + + def __init__(self, config: AgentConfig) -> None: + """Initialize developer agent.""" + super().__init__(config) + self._task_context: TaskContext | None = None + self._cell_channel_id: UUID | None = None + + async def _initialize(self) -> None: + """Initialize developer-specific resources.""" + self.log.debug("Developer agent initialized", agent_id=str(self.id)) + + async def _cleanup(self) -> None: + """Cleanup developer-specific resources.""" + self._task_context = None + self.log.debug("Developer agent cleanup complete", agent_id=str(self.id)) + + # ========================================================================= + # PHASE ENGINE IMPLEMENTATION + # ========================================================================= + + def _get_phase_configs(self) -> list[PhaseConfig[DevTaskPhase]]: + """Define the developer workflow phases.""" + return [ + PhaseConfig( + DevTaskPhase.CLAIM, + self._phase_claim, + next_phase=DevTaskPhase.UNDERSTAND, + ), + PhaseConfig( + DevTaskPhase.UNDERSTAND, + self._phase_understand, + next_phase=DevTaskPhase.PLAN, + requires_completion=True, + ), + PhaseConfig( + DevTaskPhase.PLAN, + self._phase_plan, + next_phase=DevTaskPhase.EXECUTE, + ), + PhaseConfig( + DevTaskPhase.EXECUTE, + self._phase_execute, + next_phase=DevTaskPhase.VERIFY, + requires_completion=True, + ), + PhaseConfig( + DevTaskPhase.VERIFY, + self._phase_verify, + next_phase=DevTaskPhase.NOTES, + fail_phase=DevTaskPhase.EXECUTE, # Back to execute on failure + requires_completion=True, + ), + PhaseConfig( + DevTaskPhase.NOTES, + self._phase_notes, + next_phase=None, # Terminal - developer done + ), + PhaseConfig( + DevTaskPhase.BLOCKED, + self._phase_blocked, + next_phase=DevTaskPhase.EXECUTE, # Resume execution when unblocked + requires_completion=True, + ), + ] + + def _get_current_phase(self, ctx: TaskContext) -> DevTaskPhase: + """Get the current phase from context.""" + return ctx.phase + + def _set_current_phase(self, ctx: TaskContext, phase: DevTaskPhase) -> None: + """Set the current phase in context.""" + ctx.phase = phase + + # ========================================================================= + # LIFECYCLE IMPLEMENTATION + # ========================================================================= + + async def find_work(self) -> UUID | None: + """ + SCAN phase: Find available work. + + Priority order: + 1. Own paused/interrupted tasks + 2. Assigned tasks + 3. If none, signal availability to PM + """ + self.log.info("Scanning for work") + + # Check for paused tasks first (highest priority) + paused_task = await self._find_paused_task() + if paused_task: + self.log.info("Found paused task", task_id=str(paused_task)) + return paused_task + + # Check for assigned tasks + assigned_task = await self._find_assigned_task() + if assigned_task: + self.log.info("Found assigned task", task_id=str(assigned_task)) + return assigned_task + + # Signal availability to PM + await self._signal_availability() + return None + + async def execute_task(self, task_id: UUID) -> bool: + """ + Execute task through the developer lifecycle phases. + + Returns True when developer's work is complete (submitted for QA). + QA, Documenter, and PM handle the rest of the lifecycle. + """ + # Initialize or restore task context + if self._task_context is None or self._task_context.task_id != task_id: + title, session_id = await self._get_task_info(task_id) + self._task_context = TaskContext( + task_id=task_id, + title=title, + session_id=session_id, + ) + + ctx = self._task_context + + try: + result = await self._run_phase_engine(ctx) + + if result.error: + self.log.error("Phase error", error=result.error) + ctx.blockers.append(result.error) + ctx.phase = DevTaskPhase.BLOCKED + return False + + if result.completed: + self._task_context = None + return True + + return False + + except Exception as e: + self.log.error("Error in task phase", phase=ctx.phase.value, error=str(e)) + ctx.blockers.append(str(e)) + ctx.phase = DevTaskPhase.BLOCKED + return False + + # ========================================================================= + # PHASE IMPLEMENTATIONS + # ========================================================================= + + async def _phase_claim(self, ctx: TaskContext) -> None: + """ + CLAIM phase: Lock the task and announce. + + - Claim task via /claim endpoint (validates status) + - Announce in cell channel + """ + self.log.info("CLAIM phase", task_id=str(ctx.task_id)) + + # Claim via proper endpoint (validates task is claimable) + await self._mark_claimed(ctx.task_id) + + # Announce in session + await self.send_message( + ctx.session_id, + f"Claiming TASK-{str(ctx.task_id)[:8]}: {ctx.title}", + message_type="action", + task_id=ctx.task_id, + ) + + # Journal entry + ctx.journal_entries.append( + f"[{datetime.now(UTC).isoformat()}] Claimed task. Beginning work." + ) + + async def _phase_understand(self, ctx: TaskContext) -> bool: + """ + UNDERSTAND phase: Read and comprehend requirements. + + - Read task record + - Read related code/docs + - Ask if unclear (GATE: must understand before proceeding) + + Returns True if understood, False if still clarifying. + """ + self.log.info("UNDERSTAND phase", task_id=str(ctx.task_id)) + + # Read task requirements + requirements = await self._read_task_requirements(ctx.task_id) + + # Format context using TOON for token efficiency + task_context = self.format_context_labeled( + "Task Context", + {"title": ctx.title, "requirements": requirements}, + ) + + # Use LLM to understand and identify gaps + prompt = f"""You are analyzing a task before beginning work. + +{task_context} + +Analyze: +1. What exactly needs to be done? +2. What are the acceptance criteria? +3. Is anything unclear that requires clarification? + +If everything is clear, respond with: "UNDERSTOOD: [your understanding summary]" +If clarification needed, respond with: "QUESTION: [your question]" +""" + response = await self.think(prompt) + + if response.startswith("UNDERSTOOD:"): + # Add understanding to journal + ctx.journal_entries.append( + f"[{datetime.now(UTC).isoformat()}] Understanding: {response}" + ) + return True + else: + # Ask question in session + question = response.replace("QUESTION:", "").strip() + await self.send_message( + ctx.session_id, + f"Question about TASK-{str(ctx.task_id)[:8]}: {question}", + message_type="dialogue", + task_id=ctx.task_id, + ) + return False + + async def _phase_plan(self, ctx: TaskContext) -> None: + """ + PLAN phase: Break task into subtasks. + + - Create implementation plan + - Save plan to task via API (REQUIRED before start) + - Identify dependencies and risks + - Journal the approach + """ + self.log.info("PLAN phase", task_id=str(ctx.task_id)) + + # Format context using TOON + plan_context = self.format_context_labeled( + "Task", + { + "title": ctx.title, + "understanding": ctx.journal_entries[-1] + if ctx.journal_entries + else "No context", + }, + ) + + # Use LLM to create plan - request TOON tabular response + prompt = f"""Create an implementation plan for this task: + +{plan_context} + +Break this into ordered subtasks. For each subtask provide: +- Clear description +- Files to modify +- Estimated complexity (small/medium/large) + +Format response as TOON tabular: +[N,]{{description,files,complexity}}: +Implement the main logic,src/main.py|src/utils.py,medium +Add unit tests,tests/test_main.py,small +""" + response = await self.think(prompt) + + # Parse subtasks using TOON (falls back to JSON) + try: + subtasks = self.parse_llm_response(response) + if isinstance(subtasks, list): + ctx.subtasks = subtasks + else: + ctx.subtasks = [ + {"description": response, "files": [], "complexity": "medium"} + ] + except ValueError: + # Fallback if parsing fails + ctx.subtasks = [ + {"description": response, "files": [], "complexity": "medium"} + ] + + # Analyze risks based on subtask complexity + risks = [] + complex_subtasks = [s for s in ctx.subtasks if s.get("complexity") == "high"] + if complex_subtasks: + risks.append( + f"{len(complex_subtasks)} high-complexity subtasks may need extra time" + ) + max_subtasks_per_phase = 5 + if len(ctx.subtasks) > max_subtasks_per_phase: + risks.append("Large number of subtasks - may need to split into phases") + + # Estimate sessions based on subtask count and complexity + estimated_sessions = max(1, len(ctx.subtasks) // 3 + len(complex_subtasks)) + + # Save plan to task via API (REQUIRED before start can be called) + plan_data = { + "approach": f"Implement {ctx.title}", + "steps": [s.get("description", str(s)) for s in ctx.subtasks], + "risks": risks, + "estimated_sessions": estimated_sessions, + } + await self._api_call("PATCH", f"/tasks/{ctx.task_id}", json={"plan": plan_data}) + + # Journal entry + ts = datetime.now(UTC).isoformat() + ctx.journal_entries.append(f"[{ts}] Plan: {len(ctx.subtasks)} subtasks created") + + # Announce plan + await self.send_message( + ctx.session_id, + f"TASK-{str(ctx.task_id)[:8]} plan ready: {len(ctx.subtasks)} subtasks", + message_type="action", + task_id=ctx.task_id, + ) + + async def _phase_execute(self, ctx: TaskContext) -> bool: + """ + EXECUTE phase: Work through subtasks. + + - START: Transition to in_progress on first execution + - Execute current subtask + - Commit with meaningful messages + - Update progress + + Returns True when all subtasks complete. + """ + self.log.info( + "EXECUTE phase", + task_id=str(ctx.task_id), + subtask=ctx.current_subtask, + total=len(ctx.subtasks), + ) + + # START: Transition to in_progress on first subtask + if ctx.current_subtask == 0: + await self._mark_in_progress(ctx.task_id) + self.log.info("Task started (in_progress)", task_id=str(ctx.task_id)) + + if ctx.current_subtask >= len(ctx.subtasks): + return True + + subtask = ctx.subtasks[ctx.current_subtask] + + # Format context using TOON + execute_context = self.format_context_labeled( + "Execution Context", + { + "task": ctx.title, + "subtask_number": ctx.current_subtask + 1, + "total_subtasks": len(ctx.subtasks), + "description": subtask.get("description", ""), + "files": subtask.get("files", []), + }, + ) + + # Use LLM to work on subtask + prompt = f"""Execute this subtask: + +{execute_context} + +Provide: +1. Code changes needed +2. Commands to run +3. Commit message in format: type(scope): description + +Respond with the implementation. +""" + response = await self.think_and_stream(prompt) + + # Record work done + ts = datetime.now(UTC).isoformat() + subtask_num = ctx.current_subtask + 1 + ctx.journal_entries.append(f"[{ts}] Subtask {subtask_num}: {response[:100]}...") + + # Simulate commit (in real implementation would execute git) + commit_hash = f"commit_{ctx.current_subtask}" + ctx.commits.append(commit_hash) + + # Progress update - save to task AND send message + completed = ctx.current_subtask + 1 + total = len(ctx.subtasks) + percentage = int((completed / total) * 100) if total > 0 else 0 + progress_msg = f"Completed subtask {completed}/{total}: {subtask['title']}" + + # Save progress to task (QA will see this!) + await self._add_progress(ctx.task_id, progress_msg, percentage) + + await self.send_message( + ctx.session_id, + f"TASK-{str(ctx.task_id)[:8]} ({percentage}%) {progress_msg}", + message_type="action", + task_id=ctx.task_id, + ) + + ctx.current_subtask += 1 + return ctx.current_subtask >= len(ctx.subtasks) + + async def _phase_verify(self, ctx: TaskContext) -> bool: + """ + VERIFY phase: Self-test against acceptance criteria. + + - Run quality checks (ruff, mypy, pytest) + - Self-review against acceptance criteria + - Flag for QA if passing + + Returns True if verified, False if issues found. + """ + self.log.info("VERIFY phase", task_id=str(ctx.task_id)) + + # Run quality checks (simulated) + checks = [ + ("ruff format", True), + ("ruff check", True), + ("mypy", True), + ("pytest", True), + ] + + all_passed = True + for check_name, passed in checks: + if not passed: + all_passed = False + ctx.journal_entries.append( + f"[{datetime.now(UTC).isoformat()}] VERIFY FAILED: {check_name}" + ) + + if all_passed: + # Flag for QA + await self.send_message( + ctx.session_id, + f"TASK-{str(ctx.task_id)[:8]} ready for QA review. " + f"Commits: {', '.join(ctx.commits)}", + message_type="action", + task_id=ctx.task_id, + ) + ctx.journal_entries.append( + f"[{datetime.now(UTC).isoformat()}] VERIFY PASSED. Flagged for QA." + ) + + return all_passed + + async def _phase_notes(self, ctx: TaskContext) -> None: + """ + NOTES phase: Document journey and create handoff. + + - Complete journey notes (stored in task dev_notes for QA) + - Link commits + - Create documenter handoff summary + """ + self.log.info("NOTES phase", task_id=str(ctx.task_id)) + + # Generate dev_notes for QA verification + dev_notes_prompt = f""" +Summarize the work done for QA verification: + +Task: {ctx.title} +Commits: {", ".join(ctx.commits)} +Work log: +{chr(10).join(ctx.journal_entries)} + +Create a brief summary for QA including: +1. What was built and where (files/modules) +2. Key implementation decisions +3. Tests added +4. Any gotchas or important context +""" + dev_notes = await self.think(dev_notes_prompt) + + # Generate handoff summary for documenter + handoff_prompt = f""" +Create a handoff summary for the documenter: + +Task: {ctx.title} +What was built: {dev_notes[:500]} + +Summarize in 2-3 sentences what documentation is needed. +""" + handoff_summary = await self.think(handoff_prompt) + + # Store notes in task via API (this is what QA will see!) + await self._submit_for_qa(ctx.task_id, dev_notes, handoff_summary) + + ctx.journal_entries.append( + f"[{datetime.now(UTC).isoformat()}] Submitted for QA with dev_notes" + ) + + async def _phase_blocked(self, ctx: TaskContext) -> bool: + """ + BLOCKED phase: Handle blocked state. + + - Document blocker + - Notify PM + - Wait for resolution + + Returns True if resolved. + """ + self.log.info("BLOCKED", task_id=str(ctx.task_id), blockers=ctx.blockers) + + if ctx.blockers: + blocker = ctx.blockers[-1] + await self.send_message( + ctx.session_id, + f"BLOCKED on TASK-{str(ctx.task_id)[:8]}: {blocker}", + message_type="blocker", + task_id=ctx.task_id, + ) + await self._update_task_status(ctx.task_id, TaskStatus.BLOCKED) + + # Check if blocker resolved (simulated) + resolved = False + if resolved: + ctx.blockers.clear() + + return resolved + + # ========================================================================= + # HELPER METHODS + # ========================================================================= + + async def _find_paused_task(self) -> UUID | None: + """Find own paused/interrupted tasks.""" + try: + result = await self._api_call( + "GET", + "/tasks", + params={"status": "paused", "assigned_to": str(self.id)}, + ) + tasks = result.get("items", []) + return UUID(tasks[0]["id"]) if tasks else None + except Exception as e: + self.log.warning("Failed to find paused task", error=str(e)) + return None + + async def _find_assigned_task(self) -> UUID | None: + """Find tasks assigned to this agent.""" + try: + result = await self._api_call( + "GET", + "/tasks", + params={"status": "pending", "assigned_to": str(self.id)}, + ) + tasks = result.get("items", []) + return UUID(tasks[0]["id"]) if tasks else None + except Exception as e: + self.log.warning("Failed to find assigned task", error=str(e)) + return None + + async def _signal_availability(self) -> None: + """Signal availability to orchestrator (no task context, so use API).""" + self.log.info("Signaling availability", agent_name=self.name) + # No task/session context - signal via state update instead of message + self.state.status = AgentStatus.IDLE + self.state.current_task_id = None + self.state.current_session_id = None + + async def _submit_for_qa( + self, task_id: UUID, dev_notes: str, handoff_summary: str + ) -> None: + """ + Submit task for QA review with notes. + + This stores dev_notes in the task (visible to QA) and transitions + the task to awaiting_qa status. + """ + try: + # First store dev_notes (this is what QA will see!) + combined_notes = f"{dev_notes}\n\n---\nHandoff Summary:\n{handoff_summary}" + await self._api_call( + "PATCH", + f"/tasks/{task_id}", + json={"dev_notes": combined_notes}, + ) + self.log.info("Dev notes saved to task", task_id=str(task_id)) + + # Then transition to awaiting_qa + await self._api_call("POST", f"/tasks/{task_id}/submit-qa") + self.log.info("Task submitted for QA", task_id=str(task_id)) + + except Exception as e: + self.log.error("Failed to submit for QA", error=str(e)) + raise diff --git a/roboco/agents/documenter.py b/roboco/agents/documenter.py new file mode 100644 index 00000000..ade2c340 --- /dev/null +++ b/roboco/agents/documenter.py @@ -0,0 +1,508 @@ +""" +Documenter Agent + +Implementation of the Documenter workflow from the blueprint. +Handles documentation lifecycle: + MONITOR → RECEIVE → GATHER → SYNTHESIZE → WRITE → REVIEW → PUBLISH +""" + +from datetime import UTC, datetime +from pathlib import Path +from uuid import UUID + +import aiofiles +import structlog +from roboco.agents.base import Agent, AgentConfig +from roboco.agents.mixins import PhaseConfig, PhaseEngine +from roboco.models import Team +from roboco.models.agents import ( + DocContext, + DocTaskPhase, + DocType, + DocumentSpec, +) + +logger = structlog.get_logger() + + +class DocumenterAgent(Agent, PhaseEngine[DocTaskPhase, DocContext]): + """ + Documenter agent that follows the Documenter Lifecycle. + + Workflow: + 1. MONITOR - Watch cell channel, follow development + 2. RECEIVE - Dev creates handoff, PM notifies + 3. GATHER - Pull notes, commits, conversations, QA feedback + 4. SYNTHESIZE - Understand what was built, identify docs needed + 5. WRITE - Create/update documentation + 6. REVIEW - Self-review, optional dev review + 7. PUBLISH - Documentation goes live + """ + + def __init__(self, config: AgentConfig) -> None: + """Initialize documenter agent.""" + super().__init__(config) + self._doc_context: DocContext | None = None + self._cell_channel_id: UUID | None = None + self._pending_docs: list[UUID] = [] + + async def _initialize(self) -> None: + """Initialize documenter-specific resources.""" + self.log.debug("Documenter agent initialized", agent_id=str(self.id)) + + async def _cleanup(self) -> None: + """Cleanup documenter-specific resources.""" + self._doc_context = None + self._pending_docs.clear() + self.log.debug("Documenter agent cleanup complete", agent_id=str(self.id)) + + # ========================================================================= + # PHASE ENGINE IMPLEMENTATION + # ========================================================================= + + def _get_phase_configs(self) -> list[PhaseConfig[DocTaskPhase]]: + """Define the documenter workflow phases.""" + return [ + PhaseConfig( + DocTaskPhase.RECEIVE, + self._phase_receive, + next_phase=DocTaskPhase.GATHER, + ), + PhaseConfig( + DocTaskPhase.GATHER, + self._phase_gather, + next_phase=DocTaskPhase.SYNTHESIZE, + ), + PhaseConfig( + DocTaskPhase.SYNTHESIZE, + self._phase_synthesize, + next_phase=DocTaskPhase.WRITE, + ), + PhaseConfig( + DocTaskPhase.WRITE, + self._phase_write, + next_phase=DocTaskPhase.REVIEW, + requires_completion=True, + ), + PhaseConfig( + DocTaskPhase.REVIEW, + self._phase_review, + next_phase=DocTaskPhase.PUBLISH, + ), + PhaseConfig( + DocTaskPhase.PUBLISH, + self._phase_publish, + next_phase=None, # Terminal + ), + ] + + def _get_current_phase(self, ctx: DocContext) -> DocTaskPhase: + """Get the current phase from context.""" + return ctx.phase + + def _set_current_phase(self, ctx: DocContext, phase: DocTaskPhase) -> None: + """Set the current phase in context.""" + ctx.phase = phase + + # ========================================================================= + # LIFECYCLE IMPLEMENTATION + # ========================================================================= + + async def find_work(self) -> UUID | None: + """ + MONITOR phase: Watch for documentation requests. + + - Check for tasks awaiting documentation + - Check for documentation notifications + """ + self.log.info("Monitoring for documentation requests") + + if self._pending_docs: + return self._pending_docs.pop(0) + + task_id = await self._find_awaiting_documentation() + if task_id: + return task_id + + return None + + async def execute_task(self, task_id: UUID) -> bool: + """ + Execute documentation through lifecycle phases. + + Returns True when documentation is complete. + """ + if self._doc_context is None or self._doc_context.task_id != task_id: + title, session_id = await self._get_task_info(task_id) + self._doc_context = DocContext( + task_id=task_id, + title=title, + session_id=session_id, + ) + + ctx = self._doc_context + + try: + result = await self._run_phase_engine(ctx) + + if result.error: + self.log.error( + "Error in doc phase", + phase=ctx.phase.value, + error=result.error, + ) + return False + + if result.completed: + self._doc_context = None + return True + + return False + + except Exception as e: + self.log.error( + "Error in doc phase", + phase=ctx.phase.value, + error=str(e), + ) + return False + + # ========================================================================= + # PHASE IMPLEMENTATIONS + # ========================================================================= + + async def _phase_receive(self, ctx: DocContext) -> None: + """ + RECEIVE phase: Claim documentation task. + """ + self.log.info("RECEIVE phase", task_id=str(ctx.task_id)) + + # CLAIM: Transition from awaiting_documentation to claimed + await self._mark_claimed(ctx.task_id) + + await self.send_message( + ctx.session_id, + f"Starting documentation for TASK-{str(ctx.task_id)[:8]}: {ctx.title}", + message_type="action", + task_id=ctx.task_id, + ) + + ctx.notes.append(f"[{datetime.now(UTC).isoformat()}] Documentation started") + + async def _phase_gather(self, ctx: DocContext) -> None: + """ + GATHER phase: Collect all materials. + + - Pull dev's journey notes + - Pull commits + - Pull conversations + - Pull QA feedback + - Review code changes + """ + self.log.info("GATHER phase", task_id=str(ctx.task_id)) + + # Gather all materials + ctx.dev_notes = await self._read_dev_notes(ctx.task_id) + ctx.qa_feedback = await self._read_qa_feedback(ctx.task_id) + ctx.commits = await self._get_task_commits(ctx.task_id) + ctx.conversations = await self._get_conversations(ctx.task_id) + ctx.code_changes = await self._get_code_changes(ctx.task_id) + + ctx.notes.append( + f"[{datetime.now(UTC).isoformat()}] Gathered materials: " + f"{len(ctx.commits)} commits, {len(ctx.conversations)} conversations" + ) + + async def _phase_synthesize(self, ctx: DocContext) -> None: + """ + SYNTHESIZE phase: Understand and identify docs needed. + + - What was built + - Why decisions were made + - What needs documenting + """ + self.log.info("SYNTHESIZE phase", task_id=str(ctx.task_id)) + + prompt = f""" +Analyze this completed task and determine what documentation is needed. + +Task: {ctx.title} + +Developer Notes: +{ctx.dev_notes or "None provided"} + +QA Feedback: +{ctx.qa_feedback or "None provided"} + +Commits: +{chr(10).join(ctx.commits) if ctx.commits else "None"} + +Code Changes: +{chr(10).join(ctx.code_changes) if ctx.code_changes else "None"} + +Determine: +1. Summary of what was built +2. Key decisions made +3. Documentation needed: + - API docs? (if new/changed endpoints) + - README updates? (if usage changed) + - Architecture docs? (if structure changed) + - Changelog entry? (always for features) + - Knowledge base? (for reusable learnings) + +Respond with structured analysis. +""" + response = await self.think(prompt) + ctx.summary = response + + # Determine documents needed (simplified) + ctx.documents_needed = [ + DocumentSpec( + doc_type=DocType.CHANGELOG, + title=f"Changelog entry for {ctx.title}", + path="CHANGELOG.md", + priority="required", + ), + ] + + # Add API docs if backend + if self.team == Team.BACKEND: + ctx.documents_needed.append( + DocumentSpec( + doc_type=DocType.API, + title=f"API documentation for {ctx.title}", + path="docs/backend/api/", + priority="required", + ) + ) + + # Add component docs if frontend + if self.team == Team.FRONTEND: + ctx.documents_needed.append( + DocumentSpec( + doc_type=DocType.COMPONENT, + title=f"Component documentation for {ctx.title}", + path="docs/frontend/components/", + priority="required", + ) + ) + + # PLAN: Save documentation plan to task API (required before start) + plan_data = { + "approach": f"Document {ctx.title}", + "sub_tasks": [ + { + "id": f"doc-{i}", + "title": doc.title, + "description": f"Write {doc.doc_type.value} at {doc.path}", + "completed": False, + "order": i, + } + for i, doc in enumerate(ctx.documents_needed) + ], + "risks": [], + } + await self._api_call("PATCH", f"/tasks/{ctx.task_id}", json={"plan": plan_data}) + + ctx.notes.append( + f"[{datetime.now(UTC).isoformat()}] Synthesis complete: " + f"{len(ctx.documents_needed)} documents needed" + ) + + async def _phase_write(self, ctx: DocContext) -> bool: + """ + WRITE phase: Create/update documentation. + + - START: Transition to in_progress on first doc + - Write each document + + Returns True when all docs written. + """ + self.log.info( + "WRITE phase", + task_id=str(ctx.task_id), + doc=ctx.current_doc, + total=len(ctx.documents_needed), + ) + + # START: Transition to in_progress on first doc + if ctx.current_doc == 0: + await self._mark_in_progress(ctx.task_id) + self.log.info( + "Documentation started (in_progress)", task_id=str(ctx.task_id) + ) + + if ctx.current_doc >= len(ctx.documents_needed): + return True + + doc_spec = ctx.documents_needed[ctx.current_doc] + + # Use TOON for token-efficient context encoding + doc_context = self.format_context_labeled( + "Documentation Task", + { + "title": ctx.title, + "doc_type": doc_spec.doc_type.value, + "target_path": doc_spec.path, + "summary": ctx.summary, + "dev_notes": ctx.dev_notes or "None", + }, + ) + + prompt = f"""Write documentation for this task. + +{doc_context} + +Write professional, clear documentation following best practices. +Include: +- Clear description +- Usage examples (if applicable) +- Code samples (if applicable) +- Any gotchas or notes + +Format appropriately for the document type. +""" + content = await self.think(prompt) + doc_spec.content = content + ctx.written_docs.append(doc_spec.path) + + ctx.current_doc += 1 + + progress = f"{ctx.current_doc}/{len(ctx.documents_needed)}" + await self.send_message( + ctx.session_id, + f"TASK-{str(ctx.task_id)[:8]} doc {progress}: {doc_spec.title}", + message_type="action", + task_id=ctx.task_id, + ) + + return ctx.current_doc >= len(ctx.documents_needed) + + async def _phase_review(self, ctx: DocContext) -> None: + """ + REVIEW phase: Self-review documentation. + + - Review for accuracy + - Optional dev review + """ + self.log.info("REVIEW phase", task_id=str(ctx.task_id)) + + # Self-review using LLM + for doc_spec in ctx.documents_needed: + if not doc_spec.content: + continue + + # Use TOON for token-efficient context encoding + review_context = self.format_context_labeled( + "Document Review", + { + "title": doc_spec.title, + "doc_type": doc_spec.doc_type.value, + "content": doc_spec.content, + }, + ) + + prompt = f"""Review this documentation for quality: + +{review_context} + +Check: +1. Accuracy - Does it correctly describe the feature? +2. Completeness - Is anything missing? +3. Clarity - Is it easy to understand? +4. Examples - Are examples helpful and correct? + +Format response as TOON: +{{accuracy,completeness,clarity,examples,suggestions}}: +good,complete,clear,helpful,None +""" + review = await self.think(prompt) + ts = datetime.now(UTC).isoformat() + ctx.notes.append(f"[{ts}] Reviewed {doc_spec.title}: {review[:100]}...") + + async def _phase_publish(self, ctx: DocContext) -> None: + """ + PUBLISH phase: Documentation goes live. + + - Write files to disk + - Link docs to task + - Update task status + """ + self.log.info("PUBLISH phase", task_id=str(ctx.task_id)) + + # Write documentation files + for doc_spec in ctx.documents_needed: + if doc_spec.content: + try: + path = Path(doc_spec.path) + path.parent.mkdir(parents=True, exist_ok=True) + async with aiofiles.open(path, "w") as f: + await f.write(doc_spec.content) + self.log.info("Published", path=doc_spec.path) + except Exception as e: + self.log.error( + "Failed to publish", path=doc_spec.path, error=str(e) + ) + + # Use proper docs-complete endpoint (handles notes, status) + doc_summary = f"Published: {', '.join(ctx.written_docs)}" + await self._docs_complete(ctx.task_id, doc_summary) + + await self.send_message( + ctx.session_id, + f"TASK-{str(ctx.task_id)[:8]} documentation complete, awaiting PM review\n" + f"{doc_summary}", + message_type="action", + task_id=ctx.task_id, + ) + + ctx.notes.append(f"[{datetime.now(UTC).isoformat()}] Documentation published") + + # ========================================================================= + # HELPER METHODS + # ========================================================================= + + async def _find_awaiting_documentation(self) -> UUID | None: + """Find tasks awaiting documentation.""" + try: + team_param = self.team.value if self.team else None + result = await self._api_call( + "GET", + "/tasks", + params={"status": "awaiting_documentation", "team": team_param}, + ) + tasks = result.get("items", []) + return UUID(tasks[0]["id"]) if tasks else None + except Exception as e: + self.log.warning("Failed to find awaiting documentation task", error=str(e)) + return None + + async def _read_qa_feedback(self, task_id: UUID) -> str: + """Read QA feedback.""" + try: + result = await self._api_call("GET", f"/tasks/{task_id}") + feedback: str = result.get("qa_feedback", "No QA feedback available") + return feedback + except Exception as e: + self.log.warning("Failed to read QA feedback", error=str(e)) + return "QA feedback unavailable" + + async def _get_conversations(self, task_id: UUID) -> list[str]: + """Get relevant conversations.""" + try: + result = await self._api_call("GET", f"/tasks/{task_id}/messages") + messages: list[dict[str, str]] = result.get("items", []) + return [m.get("content", "") for m in messages] + except Exception as e: + self.log.warning("Failed to get conversations", error=str(e)) + return [] + + async def _get_code_changes(self, task_id: UUID) -> list[str]: + """Get code changes from commits.""" + try: + result = await self._api_call("GET", f"/tasks/{task_id}") + changes: list[str] = result.get("code_changes", []) + return changes + except Exception as e: + self.log.warning("Failed to get code changes", error=str(e)) + return [] diff --git a/roboco/agents/factories/__init__.py b/roboco/agents/factories/__init__.py new file mode 100644 index 00000000..b004284f --- /dev/null +++ b/roboco/agents/factories/__init__.py @@ -0,0 +1,92 @@ +""" +Agent Factories + +Centralized factory functions for creating all agent types. + +Modules: +- developers: Developer agent factories +- qa: QA agent factories +- documenters: Documenter agent factories +- pms: PM agent factories (Cell PMs and Main PM) +- board: Board agent factories (Product Owner, Head Marketing, Auditor) +- cells: Cell and Organization factories +""" + +# Board agents +from roboco.agents.factories.board import ( + create_auditor, + create_head_marketing, + create_product_owner, +) + +# Cell and organization +from roboco.agents.factories.cells import ( + create_backend_cell, + create_board, + create_frontend_cell, + create_organization, + create_ux_cell, + get_agent_roster, + print_org_chart, +) + +# Developers +from roboco.agents.factories.developers import ( + create_backend_developer, + create_frontend_developer, + create_ux_developer, +) + +# Documenters +from roboco.agents.factories.documenters import ( + create_backend_documenter, + create_frontend_documenter, + create_ux_documenter, +) + +# PMs +from roboco.agents.factories.pms import ( + create_backend_pm, + create_frontend_pm, + create_main_pm, + create_ux_pm, +) + +# QA +from roboco.agents.factories.qa import ( + create_backend_qa, + create_frontend_qa, + create_ux_qa, +) + +# Organization types (re-exported for convenience) +from roboco.models.organization import Board, Cell, Organization + +__all__ = [ + "Board", + "Cell", + "Organization", + "create_auditor", + "create_backend_cell", + "create_backend_developer", + "create_backend_documenter", + "create_backend_pm", + "create_backend_qa", + "create_board", + "create_frontend_cell", + "create_frontend_developer", + "create_frontend_documenter", + "create_frontend_pm", + "create_frontend_qa", + "create_head_marketing", + "create_main_pm", + "create_organization", + "create_product_owner", + "create_ux_cell", + "create_ux_developer", + "create_ux_documenter", + "create_ux_pm", + "create_ux_qa", + "get_agent_roster", + "print_org_chart", +] diff --git a/roboco/agents/factories/_base.py b/roboco/agents/factories/_base.py new file mode 100644 index 00000000..a33d5673 --- /dev/null +++ b/roboco/agents/factories/_base.py @@ -0,0 +1,34 @@ +""" +Factory Base Utilities + +Shared utilities for agent factory functions. +""" + +import re +from pathlib import Path + + +def load_blueprint_prompt(blueprint_path: str, default_prompt: str) -> str: + """ + Load system prompt from a blueprint file. + + Args: + blueprint_path: Relative path to the blueprint markdown file + default_prompt: Default prompt if file doesn't exist or parsing fails + + Returns: + The extracted system prompt or the default + """ + path = Path(blueprint_path) + if not path.exists(): + return default_prompt + + content = path.read_text() + # Extract system prompt section (between ```blocks after ## System Prompt) + match = re.search(r"## System Prompt\s*```\s*(.*?)```", content, re.DOTALL) + return match.group(1).strip() if match else default_prompt + + +def make_slug(name: str) -> str: + """Convert a name to a URL-safe slug.""" + return name.lower().replace(" ", "-") diff --git a/roboco/agents/factories/board.py b/roboco/agents/factories/board.py new file mode 100644 index 00000000..d81b00e8 --- /dev/null +++ b/roboco/agents/factories/board.py @@ -0,0 +1,83 @@ +""" +Board Agent Factories + +Factory functions for creating board-level agents +(Product Owner, Head of Marketing, Auditor). +""" + +from roboco.agents.board import AuditorAgent, HeadMarketingAgent, ProductOwnerAgent +from roboco.agents.factories._base import load_blueprint_prompt +from roboco.models import AgentRole, Team +from roboco.models.agents import AgentConfig + + +def create_product_owner( + name: str = "Product Owner", + system_prompt: str | None = None, +) -> ProductOwnerAgent: + """Factory function to create the Product Owner agent.""" + if system_prompt is None: + system_prompt = load_blueprint_prompt( + "agents/blueprints/board/product-owner.md", + "You are the Product Owner.", + ) + + config = AgentConfig( + name=name, + slug="product-owner", + role=AgentRole.PRODUCT_OWNER, + team=Team.BOARD, + system_prompt=system_prompt, + capabilities=["requirements", "prioritization", "acceptance"], + can_notify=True, + ) + + return ProductOwnerAgent(config) + + +def create_head_marketing( + name: str = "Head of Marketing", + system_prompt: str | None = None, +) -> HeadMarketingAgent: + """Factory function to create the Head of Marketing agent.""" + if system_prompt is None: + system_prompt = load_blueprint_prompt( + "agents/blueprints/board/head-marketing.md", + "You are the Head of Marketing.", + ) + + config = AgentConfig( + name=name, + slug="head-marketing", + role=AgentRole.HEAD_MARKETING, + team=Team.BOARD, + system_prompt=system_prompt, + capabilities=["marketing", "campaigns", "analytics"], + can_notify=True, + ) + + return HeadMarketingAgent(config) + + +def create_auditor( + name: str = "Auditor", + system_prompt: str | None = None, +) -> AuditorAgent: + """Factory function to create the Auditor agent.""" + if system_prompt is None: + system_prompt = load_blueprint_prompt( + "agents/blueprints/board/auditor.md", + "You are the Auditor - the CEO's silent ally.", + ) + + config = AgentConfig( + name=name, + slug="auditor", + role=AgentRole.AUDITOR, + team=Team.BOARD, + system_prompt=system_prompt, + capabilities=["observation", "analysis", "audit", "ceo_reporting"], + can_notify=True, + ) + + return AuditorAgent(config) diff --git a/roboco/agents/factories/cells.py b/roboco/agents/factories/cells.py new file mode 100644 index 00000000..b3aa33b0 --- /dev/null +++ b/roboco/agents/factories/cells.py @@ -0,0 +1,217 @@ +""" +Cell and Organization Factories + +Factory functions for creating complete cells and the full organization. +""" + +from typing import Any + +from roboco.agents.factories.board import ( + create_auditor, + create_head_marketing, + create_product_owner, +) +from roboco.agents.factories.developers import ( + create_backend_developer, + create_frontend_developer, + create_ux_developer, +) +from roboco.agents.factories.documenters import ( + create_backend_documenter, + create_frontend_documenter, + create_ux_documenter, +) +from roboco.agents.factories.pms import ( + create_backend_pm, + create_frontend_pm, + create_main_pm, + create_ux_pm, +) +from roboco.agents.factories.qa import ( + create_backend_qa, + create_frontend_qa, + create_ux_qa, +) +from roboco.models import Team +from roboco.models.organization import Board, Cell, Organization + + +def create_backend_cell() -> Cell: + """ + Create a complete Backend cell. + + Includes: + - 1 PM (BE-PM) + - 2 Developers (BE-Dev-1, BE-Dev-2) + - 1 QA (BE-QA) + - 1 Documenter (BE-Documenter) + """ + return Cell( + name="backend-cell", + team=Team.BACKEND, + pm=create_backend_pm(), + developers=[ + create_backend_developer("BE-Dev-1"), + create_backend_developer("BE-Dev-2"), + ], + qa=create_backend_qa(), + documenter=create_backend_documenter(), + ) + + +def create_frontend_cell() -> Cell: + """ + Create a complete Frontend cell. + + Includes: + - 1 PM (FE-PM) + - 2 Developers (FE-Dev-1, FE-Dev-2) + - 1 QA (FE-QA) + - 1 Documenter (FE-Documenter) + """ + return Cell( + name="frontend-cell", + team=Team.FRONTEND, + pm=create_frontend_pm(), + developers=[ + create_frontend_developer("FE-Dev-1"), + create_frontend_developer("FE-Dev-2"), + ], + qa=create_frontend_qa(), + documenter=create_frontend_documenter(), + ) + + +def create_ux_cell() -> Cell: + """ + Create a complete UX/UI cell. + + Includes: + - 1 PM (UX-PM) + - 1 Developer (UX-Dev) + - 1 QA (UX-QA) + - 1 Documenter (UX-Documenter) + """ + return Cell( + name="uxui-cell", + team=Team.UX_UI, + pm=create_ux_pm(), + developers=[ + create_ux_developer("UX-Dev"), + ], + qa=create_ux_qa(), + documenter=create_ux_documenter(), + ) + + +def create_board() -> Board: + """ + Create the Board level. + + Includes: + - Product Owner + - Head of Marketing + - Auditor + """ + return Board( + product_owner=create_product_owner(), + head_marketing=create_head_marketing(), + auditor=create_auditor(), + ) + + +def create_organization() -> Organization: + """ + Create the complete AI organization. + + Total: 18 AI agents + - Board: 3 (Product Owner, Head of Marketing, Auditor) + - Management: 1 (Main PM) + - Backend Cell: 5 (PM, 2 Devs, QA, Documenter) + - Frontend Cell: 5 (PM, 2 Devs, QA, Documenter) + - UX/UI Cell: 4 (PM, 1 Dev, QA, Documenter) + """ + return Organization( + board=create_board(), + main_pm=create_main_pm(), + backend_cell=create_backend_cell(), + frontend_cell=create_frontend_cell(), + ux_cell=create_ux_cell(), + ) + + +def get_agent_roster() -> dict[str, list[dict[str, Any]]]: + """ + Get a roster of all agents without instantiating them. + + Useful for displaying the org structure. + """ + return { + "board": [ + {"name": "Product Owner", "role": "product_owner", "slug": "product-owner"}, + { + "name": "Head of Marketing", + "role": "head_marketing", + "slug": "head-marketing", + }, + {"name": "Auditor", "role": "auditor", "slug": "auditor"}, + ], + "management": [ + {"name": "Main PM", "role": "main_pm", "slug": "main-pm"}, + ], + "backend_cell": [ + {"name": "BE-PM", "role": "cell_pm", "slug": "be-pm"}, + {"name": "BE-Dev-1", "role": "developer", "slug": "be-dev-1"}, + {"name": "BE-Dev-2", "role": "developer", "slug": "be-dev-2"}, + {"name": "BE-QA", "role": "qa", "slug": "be-qa"}, + {"name": "BE-Documenter", "role": "documenter", "slug": "be-documenter"}, + ], + "frontend_cell": [ + {"name": "FE-PM", "role": "cell_pm", "slug": "fe-pm"}, + {"name": "FE-Dev-1", "role": "developer", "slug": "fe-dev-1"}, + {"name": "FE-Dev-2", "role": "developer", "slug": "fe-dev-2"}, + {"name": "FE-QA", "role": "qa", "slug": "fe-qa"}, + {"name": "FE-Documenter", "role": "documenter", "slug": "fe-documenter"}, + ], + "ux_cell": [ + {"name": "UX-PM", "role": "cell_pm", "slug": "ux-pm"}, + {"name": "UX-Dev", "role": "developer", "slug": "ux-dev"}, + {"name": "UX-QA", "role": "qa", "slug": "ux-qa"}, + {"name": "UX-Documenter", "role": "documenter", "slug": "ux-documenter"}, + ], + } + + +def print_org_chart() -> str: + """Generate a text-based org chart.""" + return """ + ┌─────────────┐ + │ CEO │ + │ (Human) │ + └──────┬──────┘ + │ + ┌────────────────┼────────────────┐ + │ │ │ + ┌─────▼─────┐ ┌─────▼─────┐ ┌─────▼─────┐ + │ Product │ │ Head │ │ Auditor │ + │ Owner │ │ Marketing │ │ (Spy) │ + └─────┬─────┘ └─────┬─────┘ └───────────┘ + │ │ ▲ + └───────┬────────┘ │ + │ [observes all] + ┌──────▼──────┐ + │ Main PM │ + └──────┬──────┘ + │ + ┌────────────────────┼────────────────────┐ + │ │ │ + ┌─────▼─────┐ ┌─────▼─────┐ ┌─────▼─────┐ + │ BE-PM │ │ FE-PM │ │ UX-PM │ + ├───────────┤ ├───────────┤ ├───────────┤ + │ BE-Dev x2 │ │ FE-Dev x2 │ │ UX-Dev │ + │ BE-QA │ │ FE-QA │ │ UX-QA │ + │ BE-Doc │ │ FE-Doc │ │ UX-Doc │ + └───────────┘ └───────────┘ └───────────┘ + + Total: 18 AI Agents + 1 Human CEO = 19 organization members +""" diff --git a/roboco/agents/factories/developers.py b/roboco/agents/factories/developers.py new file mode 100644 index 00000000..1ef49772 --- /dev/null +++ b/roboco/agents/factories/developers.py @@ -0,0 +1,109 @@ +""" +Developer Agent Factories + +Factory functions for creating developer agents for each team. +""" + +from roboco.agents.developer import DeveloperAgent +from roboco.agents.factories._base import load_blueprint_prompt, make_slug +from roboco.models import AgentRole, Team +from roboco.models.agents import AgentConfig + +# Blueprint paths for each team +_BLUEPRINTS = { + Team.BACKEND: "agents/blueprints/backend/be-dev.md", + Team.FRONTEND: "agents/blueprints/frontend/fe-dev.md", + Team.UX_UI: "agents/blueprints/ux_ui/ux-dev.md", +} + +# Default prompts for each team +_DEFAULT_PROMPTS = { + Team.BACKEND: "You are a backend developer.", + Team.FRONTEND: "You are a frontend developer.", + Team.UX_UI: "You are a UX/UI developer.", +} + +# Default capabilities for each team (matches blueprint capabilities) +_CAPABILITIES = { + Team.BACKEND: [ + "code_execution", + "git_operations", + "file_management", + "api_development", + "database_design", + ], + Team.FRONTEND: [ + "code_execution", + "git_operations", + "file_management", + "browser_testing", + "accessibility_testing", + "responsive_design", + ], + Team.UX_UI: [ + "design_tools", + "file_management", + "figma_expertise", + "prototyping", + "design_system_management", + "accessibility_design", + ], +} + + +def _create_developer( + name: str, + team: Team, + system_prompt: str | None = None, +) -> DeveloperAgent: + """ + Internal factory for creating a developer agent. + + Args: + name: Agent display name + team: Team assignment + system_prompt: Optional custom system prompt + + Returns: + Configured DeveloperAgent instance + """ + if system_prompt is None: + system_prompt = load_blueprint_prompt( + _BLUEPRINTS[team], + _DEFAULT_PROMPTS[team], + ) + + config = AgentConfig( + name=name, + slug=make_slug(name), + role=AgentRole.DEVELOPER, + team=team, + system_prompt=system_prompt, + capabilities=_CAPABILITIES[team], + ) + + return DeveloperAgent(config) + + +def create_backend_developer( + name: str = "BE-Dev-1", + system_prompt: str | None = None, +) -> DeveloperAgent: + """Factory function to create a backend developer agent.""" + return _create_developer(name, Team.BACKEND, system_prompt) + + +def create_frontend_developer( + name: str = "FE-Dev-1", + system_prompt: str | None = None, +) -> DeveloperAgent: + """Factory function to create a frontend developer agent.""" + return _create_developer(name, Team.FRONTEND, system_prompt) + + +def create_ux_developer( + name: str = "UX-Dev-1", + system_prompt: str | None = None, +) -> DeveloperAgent: + """Factory function to create a UX/UI developer agent.""" + return _create_developer(name, Team.UX_UI, system_prompt) diff --git a/roboco/agents/factories/documenters.py b/roboco/agents/factories/documenters.py new file mode 100644 index 00000000..ae68f3fe --- /dev/null +++ b/roboco/agents/factories/documenters.py @@ -0,0 +1,105 @@ +""" +Documenter Agent Factories + +Factory functions for creating documenter agents for each team. +""" + +from roboco.agents.documenter import DocumenterAgent +from roboco.agents.factories._base import load_blueprint_prompt, make_slug +from roboco.models import AgentRole, Team +from roboco.models.agents import AgentConfig + +# Blueprint paths for each team +_BLUEPRINTS = { + Team.BACKEND: "agents/blueprints/backend/be-documenter.md", + Team.FRONTEND: "agents/blueprints/frontend/fe-documenter.md", + Team.UX_UI: "agents/blueprints/ux_ui/ux-documenter.md", +} + +# Default prompts for each team +_DEFAULT_PROMPTS = { + Team.BACKEND: "You are a backend documenter.", + Team.FRONTEND: "You are a frontend documenter.", + Team.UX_UI: "You are a UX/UI documenter.", +} + +# Default capabilities for each team (matches blueprint capabilities) +_CAPABILITIES = { + Team.BACKEND: [ + "technical_writing", + "api_documentation", + "code_reading", + "file_management", + ], + Team.FRONTEND: [ + "technical_writing", + "component_documentation", + "code_reading", + "storybook", + "file_management", + ], + Team.UX_UI: [ + "design_documentation", + "design_system_maintenance", + "technical_writing", + "file_management", + ], +} + + +def _create_documenter( + name: str, + team: Team, + system_prompt: str | None = None, +) -> DocumenterAgent: + """ + Internal factory for creating a documenter agent. + + Args: + name: Agent display name + team: Team assignment + system_prompt: Optional custom system prompt + + Returns: + Configured DocumenterAgent instance + """ + if system_prompt is None: + system_prompt = load_blueprint_prompt( + _BLUEPRINTS[team], + _DEFAULT_PROMPTS[team], + ) + + config = AgentConfig( + name=name, + slug=make_slug(name), + role=AgentRole.DOCUMENTER, + team=team, + system_prompt=system_prompt, + capabilities=_CAPABILITIES[team], + ) + + return DocumenterAgent(config) + + +def create_backend_documenter( + name: str = "BE-Documenter", + system_prompt: str | None = None, +) -> DocumenterAgent: + """Factory function to create a backend documenter agent.""" + return _create_documenter(name, Team.BACKEND, system_prompt) + + +def create_frontend_documenter( + name: str = "FE-Documenter", + system_prompt: str | None = None, +) -> DocumenterAgent: + """Factory function to create a frontend documenter agent.""" + return _create_documenter(name, Team.FRONTEND, system_prompt) + + +def create_ux_documenter( + name: str = "UX-Documenter", + system_prompt: str | None = None, +) -> DocumenterAgent: + """Factory function to create a UX/UI documenter agent.""" + return _create_documenter(name, Team.UX_UI, system_prompt) diff --git a/roboco/agents/factories/pms.py b/roboco/agents/factories/pms.py new file mode 100644 index 00000000..198e1ee4 --- /dev/null +++ b/roboco/agents/factories/pms.py @@ -0,0 +1,107 @@ +""" +PM Agent Factories + +Factory functions for creating PM agents (Cell PMs and Main PM). +""" + +from roboco.agents.factories._base import load_blueprint_prompt, make_slug +from roboco.agents.pm import CellPMAgent, MainPMAgent +from roboco.models import AgentRole, Team +from roboco.models.agents import AgentConfig + +# Blueprint paths for cell PMs +_CELL_PM_BLUEPRINTS = { + Team.BACKEND: "agents/blueprints/backend/be-pm.md", + Team.FRONTEND: "agents/blueprints/frontend/fe-pm.md", + Team.UX_UI: "agents/blueprints/ux_ui/ux-pm.md", +} + +# Default prompts for cell PMs +_CELL_PM_PROMPTS = { + Team.BACKEND: "You are the Backend Cell PM.", + Team.FRONTEND: "You are the Frontend Cell PM.", + Team.UX_UI: "You are the UX/UI Cell PM.", +} + + +def _create_cell_pm( + name: str, + team: Team, + system_prompt: str | None = None, +) -> CellPMAgent: + """ + Internal factory for creating a cell PM agent. + + Args: + name: Agent display name + team: Team assignment + system_prompt: Optional custom system prompt + + Returns: + Configured CellPMAgent instance + """ + if system_prompt is None: + system_prompt = load_blueprint_prompt( + _CELL_PM_BLUEPRINTS[team], + _CELL_PM_PROMPTS[team], + ) + + config = AgentConfig( + name=name, + slug=make_slug(name), + role=AgentRole.CELL_PM, + team=team, + system_prompt=system_prompt, + capabilities=["task_management", "notifications"], + can_notify=True, + ) + + return CellPMAgent(config) + + +def create_backend_pm( + name: str = "BE-PM", + system_prompt: str | None = None, +) -> CellPMAgent: + """Factory function to create a backend PM agent.""" + return _create_cell_pm(name, Team.BACKEND, system_prompt) + + +def create_frontend_pm( + name: str = "FE-PM", + system_prompt: str | None = None, +) -> CellPMAgent: + """Factory function to create a frontend PM agent.""" + return _create_cell_pm(name, Team.FRONTEND, system_prompt) + + +def create_ux_pm( + name: str = "UX-PM", + system_prompt: str | None = None, +) -> CellPMAgent: + """Factory function to create a UX/UI PM agent.""" + return _create_cell_pm(name, Team.UX_UI, system_prompt) + + +def create_main_pm( + name: str = "Main PM", + system_prompt: str | None = None, +) -> MainPMAgent: + """Factory function to create the Main PM agent.""" + if system_prompt is None: + system_prompt = load_blueprint_prompt( + "agents/blueprints/board/main-pm.md", + "You are the Main PM coordinating all cells.", + ) + + config = AgentConfig( + name=name, + slug="main-pm", + role=AgentRole.MAIN_PM, + team=Team.BOARD, + system_prompt=system_prompt, + capabilities=["task_management", "notifications", "cross_cell_coordination"], + can_notify=True, + ) + + return MainPMAgent(config) diff --git a/roboco/agents/factories/qa.py b/roboco/agents/factories/qa.py new file mode 100644 index 00000000..5ebe4a2e --- /dev/null +++ b/roboco/agents/factories/qa.py @@ -0,0 +1,103 @@ +""" +QA Agent Factories + +Factory functions for creating QA agents for each team. +""" + +from roboco.agents.factories._base import load_blueprint_prompt, make_slug +from roboco.agents.qa import QAAgent +from roboco.models import AgentRole, Team +from roboco.models.agents import AgentConfig + +# Blueprint paths for each team +_BLUEPRINTS = { + Team.BACKEND: "agents/blueprints/backend/be-qa.md", + Team.FRONTEND: "agents/blueprints/frontend/fe-qa.md", + Team.UX_UI: "agents/blueprints/ux_ui/ux-qa.md", +} + +# Default prompts for each team +_DEFAULT_PROMPTS = { + Team.BACKEND: "You are a backend QA engineer.", + Team.FRONTEND: "You are a frontend QA engineer.", + Team.UX_UI: "You are a UX/UI QA engineer.", +} + +# Default capabilities for each team (matches blueprint capabilities) +_CAPABILITIES = { + Team.BACKEND: [ + "code_review", + "test_execution", + "security_analysis", + "quality_assurance", + ], + Team.FRONTEND: [ + "visual_testing", + "accessibility_testing", + "browser_testing", + "quality_assurance", + ], + Team.UX_UI: [ + "design_review", + "accessibility_review", + "quality_assurance", + ], +} + + +def _create_qa( + name: str, + team: Team, + system_prompt: str | None = None, +) -> QAAgent: + """ + Internal factory for creating a QA agent. + + Args: + name: Agent display name + team: Team assignment + system_prompt: Optional custom system prompt + + Returns: + Configured QAAgent instance + """ + if system_prompt is None: + system_prompt = load_blueprint_prompt( + _BLUEPRINTS[team], + _DEFAULT_PROMPTS[team], + ) + + config = AgentConfig( + name=name, + slug=make_slug(name), + role=AgentRole.QA, + team=team, + system_prompt=system_prompt, + capabilities=_CAPABILITIES[team], + ) + + return QAAgent(config) + + +def create_backend_qa( + name: str = "BE-QA", + system_prompt: str | None = None, +) -> QAAgent: + """Factory function to create a backend QA agent.""" + return _create_qa(name, Team.BACKEND, system_prompt) + + +def create_frontend_qa( + name: str = "FE-QA", + system_prompt: str | None = None, +) -> QAAgent: + """Factory function to create a frontend QA agent.""" + return _create_qa(name, Team.FRONTEND, system_prompt) + + +def create_ux_qa( + name: str = "UX-QA", + system_prompt: str | None = None, +) -> QAAgent: + """Factory function to create a UX/UI QA agent.""" + return _create_qa(name, Team.UX_UI, system_prompt) diff --git a/roboco/agents/factory.py b/roboco/agents/factory.py new file mode 100644 index 00000000..8e66ad58 --- /dev/null +++ b/roboco/agents/factory.py @@ -0,0 +1,59 @@ +""" +Agent Factory (Backwards Compatibility) + +This module re-exports all factories from the new location. +Use roboco.agents.factories instead for new code. +""" + +# Re-export everything from the new factories package +from roboco.agents.factories import ( + create_auditor, + create_backend_cell, + create_backend_developer, + create_backend_documenter, + create_backend_pm, + create_backend_qa, + create_board, + create_frontend_cell, + create_frontend_developer, + create_frontend_documenter, + create_frontend_pm, + create_frontend_qa, + create_head_marketing, + create_main_pm, + create_organization, + create_product_owner, + create_ux_cell, + create_ux_developer, + create_ux_documenter, + create_ux_pm, + create_ux_qa, + get_agent_roster, + print_org_chart, +) + +__all__ = [ + "create_auditor", + "create_backend_cell", + "create_backend_developer", + "create_backend_documenter", + "create_backend_pm", + "create_backend_qa", + "create_board", + "create_frontend_cell", + "create_frontend_developer", + "create_frontend_documenter", + "create_frontend_pm", + "create_frontend_qa", + "create_head_marketing", + "create_main_pm", + "create_organization", + "create_product_owner", + "create_ux_cell", + "create_ux_developer", + "create_ux_documenter", + "create_ux_pm", + "create_ux_qa", + "get_agent_roster", + "print_org_chart", +] diff --git a/roboco/agents/mixins.py b/roboco/agents/mixins.py new file mode 100644 index 00000000..2e9655d2 --- /dev/null +++ b/roboco/agents/mixins.py @@ -0,0 +1,415 @@ +""" +Agent Mixins and Abstractions + +Reusable components for agent implementations: +- PhaseEngine: Unified phase dispatch and transitions +- ContextManager: Context lifecycle management +""" + +import contextlib +from abc import abstractmethod +from collections.abc import Awaitable, Callable +from dataclasses import dataclass, field +from enum import Enum +from typing import Any +from uuid import UUID + +# ============================================================================= +# PHASE ENGINE +# ============================================================================= + + +@dataclass +class PhaseConfig[PhaseT: Enum]: + """ + Configuration for a phase in the workflow. + + Attributes: + phase: The phase enum value + handler: Async function to execute for this phase + next_phase: Phase to transition to after completion (None = terminal) + fail_phase: Phase to transition to if handler returns False (optional) + requires_completion: If True, handler must return True to advance + """ + + phase: PhaseT + handler: Callable[..., Awaitable[bool | None]] + next_phase: PhaseT | None = None + fail_phase: PhaseT | None = None + requires_completion: bool = False + + +@dataclass +class PhaseResult: + """Result of running a phase.""" + + completed: bool = False # True if workflow complete + advanced: bool = False # True if phase advanced + error: str | None = None + + +class PhaseEngine[PhaseT: Enum, ContextT]: + """ + Mixin for phase-based workflow execution. + + Provides unified phase dispatch and transition logic that can be + configured per agent type. Replaces duplicate _dispatch_phase and + _run_phase implementations across agents. + + Usage: + class MyAgent(Agent, PhaseEngine[MyPhase, MyContext]): + def _get_phase_configs(self) -> list[PhaseConfig[MyPhase]]: + return [ + PhaseConfig(MyPhase.START, self._phase_start, MyPhase.WORK), + PhaseConfig(MyPhase.WORK, self._phase_work, MyPhase.END, + requires_completion=True), + PhaseConfig(MyPhase.END, self._phase_end, None), + ] + """ + + @abstractmethod + def _get_phase_configs(self) -> list[PhaseConfig[PhaseT]]: + """ + Define the phase workflow configuration. + + Returns: + List of PhaseConfig defining handlers and transitions. + """ + ... + + @abstractmethod + def _get_current_phase(self, ctx: ContextT) -> PhaseT: + """Get the current phase from context.""" + ... + + @abstractmethod + def _set_current_phase(self, ctx: ContextT, phase: PhaseT) -> None: + """Set the current phase in context.""" + ... + + async def _run_phase_engine(self, ctx: ContextT) -> PhaseResult: + """ + Execute the current phase and handle transitions. + + Args: + ctx: The workflow context + + Returns: + PhaseResult indicating completion status + """ + configs = {cfg.phase: cfg for cfg in self._get_phase_configs()} + current_phase = self._get_current_phase(ctx) + + config = configs.get(current_phase) + if not config: + return PhaseResult(error=f"No config for phase: {current_phase}") + + try: + # Execute handler + result = await config.handler(ctx) + + # Terminal phase check + if config.next_phase is None: + return PhaseResult(completed=True) + + # Check if we should advance + should_advance = True + if config.requires_completion: + should_advance = result is True + + if should_advance: + self._set_current_phase(ctx, config.next_phase) + return PhaseResult(advanced=True) + + # Handle failure transition (e.g., VERIFY fails → back to EXECUTE) + if config.fail_phase is not None and result is False: + self._set_current_phase(ctx, config.fail_phase) + return PhaseResult(advanced=True) + + return PhaseResult() + + except Exception as e: + return PhaseResult(error=str(e)) + + +# ============================================================================= +# CYCLIC PHASE RUNNER (for continuous-duty agents like PM/Board) +# ============================================================================= + + +@dataclass +class CyclicPhaseConfig[PhaseT: Enum]: + """ + Configuration for a phase in a continuous cycle. + + Unlike PhaseConfig, this is for agents that cycle forever + (PM, Board agents) rather than completing tasks. + """ + + phase: PhaseT + handler: Callable[..., Awaitable[None]] + next_phase: PhaseT + + +class CyclicPhaseRunner[PhaseT: Enum]: + """ + Mixin for continuous-duty agents that cycle through phases. + + Unlike PhaseEngine which handles task completion, this is for + agents like PM and Board that run continuously. + + Usage: + class CellPMAgent(Agent, CyclicPhaseRunner[CellPMPhase]): + _current_phase: CellPMPhase = CellPMPhase.MONITOR + + def _get_cyclic_phase_configs(self) -> list[CyclicPhaseConfig]: + return [ + CyclicPhaseConfig(CellPMPhase.MONITOR, self._phase_monitor, + CellPMPhase.TRIAGE), + # ... etc + ] + """ + + _current_phase: PhaseT + + @abstractmethod + def _get_cyclic_phase_configs(self) -> list[CyclicPhaseConfig[PhaseT]]: + """Define the cyclic phase workflow.""" + ... + + async def _run_phase_cycle(self) -> str | None: + """ + Execute the current phase and advance to next. + + Returns error message if any, None on success. + """ + configs = {cfg.phase: cfg for cfg in self._get_cyclic_phase_configs()} + + config = configs.get(self._current_phase) + if not config: + return f"No config for phase: {self._current_phase}" + + try: + await config.handler() + self._current_phase = config.next_phase + return None + except Exception as e: + return str(e) + + +# ============================================================================= +# CONTEXT MANAGER +# ============================================================================= + + +@dataclass +class BaseContext: + """Base context with common fields.""" + + task_id: UUID + title: str = "" + notes: list[str] = field(default_factory=list) + + +class ContextManager[ContextT]: + """ + Mixin for managing workflow context lifecycle. + + Handles: + - Context initialization/restoration + - Context cleanup on completion + - Type-safe context access + + Usage: + class MyAgent(Agent, ContextManager[MyContext]): + _context: MyContext | None = None + + def _create_context(self, task_id: UUID, title: str) -> MyContext: + return MyContext(task_id=task_id, title=title) + + async def execute_task(self, task_id: UUID) -> bool: + ctx = await self._ensure_context(task_id) + # ... work with ctx + """ + + _context: ContextT | None + + @abstractmethod + def _create_context(self, task_id: UUID, title: str) -> ContextT: + """Create a new context instance.""" + ... + + @abstractmethod + def _get_context_task_id(self, ctx: ContextT) -> UUID: + """Get the task ID from a context.""" + ... + + @abstractmethod + async def _get_task_title(self, task_id: UUID) -> str: + """Get task title from API (implemented in base Agent).""" + ... + + async def _ensure_context(self, task_id: UUID) -> ContextT: + """ + Ensure context exists for the given task. + + Creates new context if none exists or if task ID changed. + """ + if self._context is None or self._get_context_task_id(self._context) != task_id: + title = await self._get_task_title(task_id) + self._context = self._create_context(task_id, title) + return self._context + + def _clear_context(self) -> None: + """Clear the current context.""" + self._context = None + + @property + def context(self) -> ContextT | None: + """Get the current context (may be None).""" + return self._context + + def require_context(self) -> ContextT: + """Get context, raising if None.""" + if self._context is None: + raise RuntimeError("No active context") + return self._context + + +# ============================================================================= +# WORK FINDER +# ============================================================================= + + +@dataclass +class WorkSearchStrategy: + """ + A strategy for finding work. + + Attributes: + name: Descriptive name for logging + finder: Async function that returns task ID or None + priority: Lower = higher priority + """ + + name: str + finder: Callable[[], Awaitable[UUID | None]] + priority: int = 0 + + +class WorkFinder: + """ + Mixin for finding work with prioritized strategies. + + Replaces duplicate find_work implementations with a configurable + search strategy pattern. + + Usage: + class MyAgent(Agent, WorkFinder): + def _get_work_strategies(self) -> list[WorkSearchStrategy]: + return [ + WorkSearchStrategy("paused", self._find_paused, priority=0), + WorkSearchStrategy("assigned", self._find_assigned, priority=1), + ] + """ + + _pending_work: list[UUID] + + @abstractmethod + def _get_work_strategies(self) -> list[WorkSearchStrategy]: + """Define work search strategies in priority order.""" + ... + + async def _find_work_prioritized(self) -> UUID | None: + """ + Find work using prioritized strategies. + + First checks pending work queue, then tries each strategy + in priority order. + """ + # Check pending queue first + if hasattr(self, "_pending_work") and self._pending_work: + return self._pending_work.pop(0) + + # Try strategies in priority order + strategies = sorted(self._get_work_strategies(), key=lambda s: s.priority) + for strategy in strategies: + task_id = await strategy.finder() + if task_id: + return task_id + + return None + + +# ============================================================================= +# PROGRESS TRACKER +# ============================================================================= + + +@dataclass +class ProgressUpdate: + """A progress update for a task.""" + + message: str + percentage: int + details: dict[str, Any] = field(default_factory=dict) + + +class ProgressTracker: + """ + Mixin for tracking and reporting progress. + + Provides unified progress reporting that saves to task AND + sends channel messages. + """ + + @abstractmethod + async def _api_call(self, method: str, path: str, **kwargs: Any) -> dict[str, Any]: + """Make API call (implemented in base Agent).""" + ... + + @abstractmethod + async def send_message( + self, + session_id: UUID | None, + content: str, + message_type: str, + task_id: UUID | None = None, + ) -> None: + """Send message to session (implemented in base Agent).""" + ... + + async def _report_progress( + self, + task_id: UUID, + session_id: UUID | None, + message: str, + percentage: int, + ) -> None: + """ + Report progress for a task. + + Saves to task record AND sends message to session. + + Args: + task_id: Task to update + session_id: Session to notify (from context) + message: Progress message + percentage: Completion percentage (0-100) + """ + # Save to task (suppress errors - logged by _api_call) + with contextlib.suppress(Exception): + await self._api_call( + "POST", + f"/tasks/{task_id}/progress", + json={"message": message, "percentage": percentage}, + ) + + # Send session message + task_ref = str(task_id)[:8] + await self.send_message( + session_id, + f"TASK-{task_ref} ({percentage}%) {message}", + message_type="action", + task_id=task_id, + ) diff --git a/roboco/agents/orchestrator.py b/roboco/agents/orchestrator.py new file mode 100644 index 00000000..c9be2846 --- /dev/null +++ b/roboco/agents/orchestrator.py @@ -0,0 +1,387 @@ +""" +Agent Orchestrator + +Manages the lifecycle of all agents in the system. +Handles spawning, monitoring, and coordination. +""" + +import asyncio +import contextlib +from collections.abc import Callable +from datetime import UTC, datetime +from typing import Any +from uuid import UUID + +import structlog +from roboco.agents.base import Agent +from roboco.models import AgentRole, AgentStatus, Team + +logger = structlog.get_logger() + + +class Orchestrator: + """ + Central orchestrator for all RoboCo agents. + + Responsibilities: + - Spawn and stop agents + - Monitor agent health + - Route messages between agents + - Handle agent failures and restarts + """ + + def __init__(self) -> None: + """Initialize the orchestrator.""" + self._agents: dict[UUID, Agent] = {} + self._agent_tasks: dict[UUID, asyncio.Task] = {} + self._running = False + self._monitor_task: asyncio.Task | None = None + + self.log = logger.bind(component="orchestrator") + + @property + def agents(self) -> dict[UUID, Agent]: + """Get all registered agents.""" + return self._agents.copy() + + @property + def active_agents(self) -> list[Agent]: + """Get all active agents.""" + return [ + a for a in self._agents.values() if a.state.status == AgentStatus.ACTIVE + ] + + @property + def idle_agents(self) -> list[Agent]: + """Get all idle agents.""" + return [a for a in self._agents.values() if a.state.status == AgentStatus.IDLE] + + # ========================================================================= + # LIFECYCLE + # ========================================================================= + + async def start(self) -> None: + """Start the orchestrator.""" + if self._running: + self.log.warning("Orchestrator already running") + return + + self.log.info("Starting orchestrator") + self._running = True + + # Start health monitor + self._monitor_task = asyncio.create_task(self._health_monitor()) + + async def stop(self) -> None: + """Stop the orchestrator and all agents.""" + if not self._running: + return + + self.log.info("Stopping orchestrator") + self._running = False + + # Stop health monitor + if self._monitor_task: + self._monitor_task.cancel() + with contextlib.suppress(asyncio.CancelledError): + await self._monitor_task + + # Stop all agents + await self.stop_all_agents() + + self.log.info("Orchestrator stopped") + + # ========================================================================= + # AGENT MANAGEMENT + # ========================================================================= + + def register_agent(self, agent: Agent) -> None: + """ + Register an agent with the orchestrator. + + Args: + agent: The agent to register + """ + if agent.id in self._agents: + self.log.warning("Agent already registered", agent_id=str(agent.id)) + return + + self._agents[agent.id] = agent + self.log.info( + "Agent registered", + agent_id=str(agent.id), + agent_name=agent.name, + agent_role=agent.role.value, + ) + + def unregister_agent(self, agent_id: UUID) -> None: + """ + Unregister an agent. + + Args: + agent_id: ID of the agent to unregister + """ + if agent_id not in self._agents: + return + + agent = self._agents.pop(agent_id) + self.log.info( + "Agent unregistered", agent_id=str(agent_id), agent_name=agent.name + ) + + async def spawn_agent(self, agent: Agent) -> None: + """ + Spawn an agent (register and start). + + Args: + agent: The agent to spawn + """ + self.register_agent(agent) + await agent.start() + self.log.info("Agent spawned", agent_id=str(agent.id), agent_name=agent.name) + + async def stop_agent(self, agent_id: UUID) -> None: + """ + Stop a specific agent. + + Args: + agent_id: ID of the agent to stop + """ + if agent_id not in self._agents: + self.log.warning("Agent not found", agent_id=str(agent_id)) + return + + agent = self._agents[agent_id] + await agent.stop() + self.log.info("Agent stopped", agent_id=str(agent_id), agent_name=agent.name) + + async def stop_all_agents(self) -> None: + """Stop all registered agents.""" + self.log.info("Stopping all agents", count=len(self._agents)) + + # Stop all agents concurrently + await asyncio.gather( + *[agent.stop() for agent in self._agents.values()], + return_exceptions=True, + ) + + async def restart_agent(self, agent_id: UUID) -> None: + """ + Restart an agent. + + Args: + agent_id: ID of the agent to restart + """ + if agent_id not in self._agents: + self.log.warning("Agent not found", agent_id=str(agent_id)) + return + + agent = self._agents[agent_id] + self.log.info("Restarting agent", agent_id=str(agent_id), agent_name=agent.name) + + await agent.stop() + await asyncio.sleep(1) # Brief pause + await agent.start() + + # ========================================================================= + # QUERYING + # ========================================================================= + + def get_agent(self, agent_id: UUID) -> Agent | None: + """Get an agent by ID.""" + return self._agents.get(agent_id) + + def get_agent_by_slug(self, slug: str) -> Agent | None: + """Get an agent by slug.""" + for agent in self._agents.values(): + if agent.config.slug == slug: + return agent + return None + + def get_agents_by_role(self, role: AgentRole) -> list[Agent]: + """Get all agents with a specific role.""" + return [a for a in self._agents.values() if a.role == role] + + def get_agents_by_team(self, team: Team) -> list[Agent]: + """Get all agents in a specific team.""" + return [a for a in self._agents.values() if a.team == team] + + def get_cell_agents(self, team: Team) -> dict[str, list[Agent]]: + """ + Get agents organized by role for a team/cell. + + Returns: + Dict with keys: developers, qa, pm, documenter + """ + team_agents = self.get_agents_by_team(team) + return { + "developers": [a for a in team_agents if a.role == AgentRole.DEVELOPER], + "qa": [a for a in team_agents if a.role == AgentRole.QA], + "pm": [a for a in team_agents if a.role == AgentRole.CELL_PM], + "documenter": [a for a in team_agents if a.role == AgentRole.DOCUMENTER], + } + + # ========================================================================= + # HEALTH MONITORING + # ========================================================================= + + async def _health_monitor(self) -> None: + """ + Monitor agent health periodically. + + Checks for: + - Unresponsive agents + - Agents with errors + - Agents that need restart + """ + while self._running: + try: + await self._check_agent_health() + await asyncio.sleep(30) # Check every 30 seconds + except asyncio.CancelledError: + break + except Exception as e: + self.log.error("Error in health monitor", error=str(e)) + await asyncio.sleep(5) + + async def _check_agent_health(self) -> None: + """Check health of all agents.""" + now = datetime.now(UTC) + + for agent in self._agents.values(): + # Check for errors + if agent.state.error: + self.log.warning( + "Agent has error", + agent_id=str(agent.id), + error=agent.state.error, + ) + + # Check for inactivity (5 minutes) + minutes_in_seconds = 300 + if agent.state.last_activity: + inactive_seconds = (now - agent.state.last_activity).total_seconds() + if inactive_seconds > minutes_in_seconds and agent.is_running: + self.log.warning( + "Agent inactive", + agent_id=str(agent.id), + inactive_seconds=inactive_seconds, + ) + + def get_health_status(self) -> dict[str, Any]: + """ + Get overall health status of all agents. + + Returns: + Health status summary + """ + total = len(self._agents) + by_status: dict[str, int] = {} + errors: list[dict[str, str]] = [] + + for agent in self._agents.values(): + status = agent.state.status.value + by_status[status] = by_status.get(status, 0) + 1 + + if agent.state.error: + errors.append( + { + "agent_id": str(agent.id), + "agent_name": agent.name, + "error": agent.state.error, + } + ) + + return { + "total_agents": total, + "by_status": by_status, + "errors": errors, + "healthy": len(errors) == 0, + } + + # ========================================================================= + # CELL MANAGEMENT + # ========================================================================= + + async def spawn_cell( + self, + team: Team, + agent_factory: Callable[[Team], list[Agent]], + ) -> list[Agent]: + """ + Spawn all agents for a cell. + + Args: + team: The team/cell to spawn + agent_factory: Factory function that creates agents for the team + + Returns: + List of spawned agents + """ + self.log.info("Spawning cell", team=team.value) + + agents = agent_factory(team) + for agent in agents: + await self.spawn_agent(agent) + + return agents + + async def stop_cell(self, team: Team) -> None: + """ + Stop all agents in a cell. + + Args: + team: The team/cell to stop + """ + self.log.info("Stopping cell", team=team.value) + + team_agents = self.get_agents_by_team(team) + await asyncio.gather( + *[self.stop_agent(a.id) for a in team_agents], + return_exceptions=True, + ) + + # ========================================================================= + # SERIALIZATION + # ========================================================================= + + def to_dict(self) -> dict[str, Any]: + """Get orchestrator status as dictionary.""" + return { + "running": self._running, + "total_agents": len(self._agents), + "agents": [a.to_dict() for a in self._agents.values()], + "health": self.get_health_status(), + } + + +# ============================================================================= +# SINGLETON HOLDER +# ============================================================================= + + +class _OrchestratorHolder: + """Holder class for singleton orchestrator instance.""" + + instance: Orchestrator | None = None + + +def get_orchestrator() -> Orchestrator: + """Get or create the global orchestrator instance.""" + if _OrchestratorHolder.instance is None: + _OrchestratorHolder.instance = Orchestrator() + return _OrchestratorHolder.instance + + +async def start_orchestrator() -> Orchestrator: + """Start the global orchestrator.""" + orchestrator = get_orchestrator() + await orchestrator.start() + return orchestrator + + +async def stop_orchestrator() -> None: + """Stop the global orchestrator.""" + if _OrchestratorHolder.instance: + await _OrchestratorHolder.instance.stop() + _OrchestratorHolder.instance = None diff --git a/roboco/agents/pm.py b/roboco/agents/pm.py new file mode 100644 index 00000000..955d9128 --- /dev/null +++ b/roboco/agents/pm.py @@ -0,0 +1,1537 @@ +""" +PM Agents (Cell PM and Main PM) + +Implementation of PM workflows from the blueprint. +Cell PM: + MONITOR → TRIAGE → ASSIGN → FACILITATE → ESCALATE → TRACK → REPORT +Main PM: + OVERSEE → RECEIVE → PRIORITIZE → COORDINATE → DISTRIBUTE → REPORT UP → FACILITATE +""" + +from typing import Any +from uuid import UUID + +import structlog +from roboco.agents.base import Agent, AgentConfig +from roboco.agents.mixins import CyclicPhaseConfig, CyclicPhaseRunner +from roboco.models import NotificationType, TaskStatus +from roboco.models.agents import ( + CellPMPhase, + CellStatus, + Escalation, + MainPMPhase, + TaskAssignment, +) + +logger = structlog.get_logger() + + +class CellPMAgent(Agent, CyclicPhaseRunner[CellPMPhase]): + """ + Cell PM agent that manages a single cell (Backend, Frontend, or UX/UI). + + Workflow: + 1. MONITOR - Watch cell channel, track tasks, check health + 2. TRIAGE - Assess new tasks, prioritize + 3. ASSIGN - Match tasks to devs + 4. FACILITATE - Answer questions, remove blockers + 5. ESCALATE - Escalate beyond cell's control to Main PM + 6. TRACK - Monitor progress, update estimates + 7. REPORT - Status to Main PM + """ + + def __init__(self, config: AgentConfig) -> None: + """Initialize Cell PM agent.""" + super().__init__(config) + self._current_phase = CellPMPhase.MONITOR + self._cell_status = CellStatus(name=self.cell_name) + self._pending_tasks: list[UUID] = [] + self._pending_escalations: list[Escalation] = [] + self._cell_channel_id: UUID | None = None + + async def _initialize(self) -> None: + """Initialize Cell PM-specific resources.""" + self.log.debug("Cell PM agent initialized", agent_id=str(self.id)) + + async def _cleanup(self) -> None: + """Cleanup Cell PM-specific resources.""" + self._pending_tasks.clear() + self._pending_escalations.clear() + self.log.debug("Cell PM agent cleanup complete", agent_id=str(self.id)) + + # ========================================================================= + # CYCLIC PHASE RUNNER IMPLEMENTATION + # ========================================================================= + + def _get_cyclic_phase_configs(self) -> list[CyclicPhaseConfig[CellPMPhase]]: + """Define the Cell PM workflow phases.""" + return [ + CyclicPhaseConfig( + CellPMPhase.MONITOR, + self._phase_monitor, + CellPMPhase.TRIAGE, + ), + CyclicPhaseConfig( + CellPMPhase.TRIAGE, + self._phase_triage, + CellPMPhase.ASSIGN, + ), + CyclicPhaseConfig( + CellPMPhase.ASSIGN, + self._phase_assign, + CellPMPhase.FACILITATE, + ), + CyclicPhaseConfig( + CellPMPhase.FACILITATE, + self._phase_facilitate, + CellPMPhase.ESCALATE, + ), + CyclicPhaseConfig( + CellPMPhase.ESCALATE, + self._phase_escalate, + CellPMPhase.TRACK, + ), + CyclicPhaseConfig( + CellPMPhase.TRACK, + self._phase_track, + CellPMPhase.REPORT, + ), + CyclicPhaseConfig( + CellPMPhase.REPORT, + self._phase_report, + CellPMPhase.MONITOR, # Cycle back + ), + ] + + # ========================================================================= + # LIFECYCLE IMPLEMENTATION + # ========================================================================= + + async def find_work(self) -> UUID | None: + """ + Find work for the PM. + + Priority: + 1. Paused tasks with all subtasks complete (ready for closure) + 2. Assigned tasks in progress + 3. Fall back to cyclic management duties (self.id) + """ + # Check for paused tasks ready for closure + ready_task = await self._find_paused_task_ready_for_closure() + if ready_task: + self.log.info( + "Found paused task ready for closure", task_id=str(ready_task) + ) + return ready_task + + # Check for assigned in-progress tasks + assigned_task = await self._find_assigned_task() + if assigned_task: + self.log.info("Found assigned task", task_id=str(assigned_task)) + return assigned_task + + # Fall back to cyclic management duties + return self.id + + async def _find_paused_task_ready_for_closure(self) -> UUID | None: + """ + Find own paused tasks where all subtasks are complete. + + This is the key PM workflow: delegate subtasks, pause, get respawned + when subtasks complete, then review and close. + """ + try: + # Get my paused tasks + result = await self._api_call( + "GET", + "/tasks", + params={"status": "paused", "assigned_to": str(self.id)}, + ) + paused_tasks = ( + result.get("items", result) if isinstance(result, dict) else result + ) + + for task in paused_tasks: + task_id = task.get("id") + if not task_id: + continue + + # Check if this task has subtasks + subtasks_result = await self._api_call( + "GET", + "/tasks", + params={"parent_task_id": task_id}, + ) + subtasks = ( + subtasks_result.get("items", subtasks_result) + if isinstance(subtasks_result, dict) + else subtasks_result + ) + + if not subtasks: + continue # No subtasks - not a delegation task + + # Check if ALL subtasks are complete + all_complete = all( + s.get("status") in ("completed", "cancelled") for s in subtasks + ) + + if all_complete: + return UUID(task_id) if isinstance(task_id, str) else task_id + + return None + + except Exception as e: + self.log.warning( + "Failed to find paused tasks ready for closure", error=str(e) + ) + return None + + async def _find_assigned_task(self) -> UUID | None: + """Find tasks assigned to this PM that are in progress.""" + try: + result = await self._api_call( + "GET", + "/tasks", + params={"status": "in_progress", "assigned_to": str(self.id)}, + ) + tasks = result.get("items", result) if isinstance(result, dict) else result + return UUID(tasks[0]["id"]) if tasks else None + except Exception as e: + self.log.warning("Failed to find assigned task", error=str(e)) + return None + + async def execute_task(self, task_id: UUID) -> bool: + """ + Execute PM work. + + Two modes: + 1. task_id == self.id: Run cyclic management duties + 2. task_id is real task: Work on specific task (CLAIM → PLAN → START → ...) + + Returns True when task-specific work is complete, False for cyclic duties. + """ + # Cyclic management duties (no specific task) + if task_id == self.id: + error = await self._run_phase_cycle() + if error: + self.log.error( + "Error in PM phase", phase=self._current_phase.value, error=error + ) + return False # Never complete - continuous duty + + # Task-specific work - delegate to task workflow + return await self._execute_pm_task(task_id) + + async def _execute_pm_task(self, task_id: UUID) -> bool: + """ + Execute PM workflow on a specific task. + + PM Workflow: CLAIM → PLAN → START → EXECUTE (delegate) → PAUSE → COMPLETE + + Returns True when PM work is done (delegated or completed). + """ + try: + task = await self._api_call("GET", f"/tasks/{task_id}") + status = task.get("status") + return await self._handle_pm_task_status(task_id, task, status) + except Exception as e: + self.log.error( + "Error in PM task execution", task_id=str(task_id), error=str(e) + ) + return False + + async def _handle_pm_task_status( + self, task_id: UUID, task: dict[str, Any], status: str | None + ) -> bool: + """Handle PM task based on current status.""" + if status == "pending": + await self._mark_claimed(task_id) + self.log.info("PM claimed task", task_id=str(task_id)) + return False + + if status == "claimed": + return await self._handle_claimed_task(task_id, task) + + if status == "in_progress": + return await self._handle_in_progress_task(task_id, task) + + if status == "paused": + await self._mark_completed(task_id) + self.log.info("PM completed task", task_id=str(task_id)) + return True + + return False + + async def _handle_claimed_task(self, task_id: UUID, task: dict[str, Any]) -> bool: + """Handle claimed task - plan then start.""" + if not task.get("plan"): + plan = await self._create_pm_plan(task) + await self._api_call("PATCH", f"/tasks/{task_id}", json={"plan": plan}) + self.log.info("PM planned task", task_id=str(task_id)) + return False + + await self._mark_in_progress(task_id) + self.log.info("PM started task", task_id=str(task_id)) + return False + + async def _handle_in_progress_task( + self, task_id: UUID, task: dict[str, Any] + ) -> bool: + """Handle in_progress task - delegate and pause.""" + delegated = await self._delegate_task(task_id, task) + if delegated: + remaining = ["Review subtask completions", "Close task"] + await self._api_call( + "POST", + f"/tasks/{task_id}/pause", + json={ + "reason": "Awaiting subtask completion", + "checkpoint_summary": "Delegated to cell agents", + "remaining_work": remaining, + }, + ) + self.log.info("PM delegated and paused", task_id=str(task_id)) + return True + + async def _create_pm_plan(self, task: dict[str, Any]) -> dict[str, Any]: + """Create a PM triage plan for a task.""" + return { + "approach": "Triage and delegate to cell developers", + "steps": [ + "Analyze requirements", + "Identify subtasks", + "Assign to available developers", + "Create work session", + "Monitor progress", + ], + "risks": task.get("acceptance_criteria", [])[:2], + "estimated_sessions": 1, + } + + async def _delegate_task(self, task_id: UUID, task: dict[str, Any]) -> bool: + """Delegate task by creating subtasks for developers. + + Follows blueprint workflow: + CREATE (backlog) → SESSION → ACTIVATE (pending) → NOTIFY + """ + # Find available developer + best_dev = await self._find_best_dev(task_id) + if not best_dev: + self.log.warning("No available developer for task", task_id=str(task_id)) + return False + + team = self.team.value if self.team else "backend" + + # Step 1: Create subtask with status "backlog" (prevents premature pickup) + subtask_resp = await self._api_call( + "POST", + "/tasks", + json={ + "title": f"Implement: {task.get('title', 'Task')}", + "description": task.get("description", ""), + "team": team, + "acceptance_criteria": task.get("acceptance_criteria", []), + "parent_task_id": str(task_id), + "assigned_to": str(best_dev.agent_id), + "status": "backlog", # Backlog until session is ready + }, + ) + subtask_id = subtask_resp.get("id") + if not subtask_id: + self.log.error("Failed to create subtask", parent_task_id=str(task_id)) + return False + + self.log.info( + "PM created subtask (backlog)", + subtask_id=subtask_id, + parent_task_id=str(task_id), + assigned_to=str(best_dev.agent_id), + ) + + # Step 2: Create session for the subtask + channel_slug = self._get_team_channel(team) + try: + await self._api_call( + "POST", + "/sessions/for-tasks", + json={ + "task_ids": [subtask_id], + "channel_slug": channel_slug, + "scope": f"Work session for {task.get('title', 'task')}", + "relationship_type": "implements", + }, + ) + self.log.info("Session created for subtask", subtask_id=subtask_id) + except Exception as e: + self.log.warning( + "Session creation failed, activating anyway", + subtask_id=subtask_id, + error=str(e), + ) + + # Step 3: Activate subtask (changes status to pending) + try: + await self._api_call("POST", f"/tasks/{subtask_id}/activate") + self.log.info("Subtask activated", subtask_id=subtask_id) + except Exception as e: + self.log.error( + "Failed to activate subtask", + subtask_id=subtask_id, + error=str(e), + ) + return False + + # Step 4: Notify assigned developer + try: + await self._notify_developer(best_dev, subtask_id, task) + except Exception as e: + self.log.warning( + "Failed to notify developer (task still assigned)", + error=str(e), + ) + + return True + + def _get_team_channel(self, team: str) -> str: + """Get the channel slug for a team.""" + channel_map = { + "backend": "backend-cell", + "frontend": "frontend-cell", + "ux_ui": "uxui-cell", + } + return channel_map.get(team, "backend-cell") + + async def _notify_developer( + self, dev: Any, subtask_id: str, task: dict[str, Any] + ) -> None: + """Notify developer of new task assignment.""" + await self._api_call( + "POST", + "/notifications", + json={ + "type": "task_assigned", + "priority": "normal", + "to_agents": [str(dev.agent_id)], + "subject": f"New task: {task.get('title', 'Task')}", + "body": ( + f"You've been assigned a new task.\n\n" + f"Task ID: {subtask_id}\n" + f"Title: {task.get('title', 'Task')}\n\n" + f"Use roboco_task_scan to find and claim it." + ), + "related_task_id": subtask_id, + "requires_ack": False, + }, + ) + + # ========================================================================= + # CELL PM PHASES + # ========================================================================= + + async def _phase_monitor(self) -> None: + """ + MONITOR phase: Watch cell health. + + - Watch cell channel + - Track active tasks + - Check for blockers + """ + self.log.debug("MONITOR phase") + + # Update cell status + self._cell_status.active_tasks = await self._count_active_tasks() + self._cell_status.blocked_tasks = await self._count_blocked_tasks() + self._cell_status.available_devs = await self._count_available_devs() + + # Check for concerning patterns + if self._cell_status.blocked_tasks > 0: + self._cell_status.concerns.append( + f"{self._cell_status.blocked_tasks} blocked tasks" + ) + + async def _phase_triage(self) -> None: + """ + TRIAGE phase: Assess and prioritize new tasks. + """ + self.log.debug("TRIAGE phase") + + # Get unassigned tasks + new_tasks = await self._get_unassigned_tasks() + + for task_id in new_tasks: + # Use TOON for token-efficient context encoding + triage_context = self.format_context_labeled( + "Task Triage", + {"task_id": str(task_id), "cell": self.cell_name}, + ) + + prompt = f"""Assess this task for prioritization: + +{triage_context} + +Consider: +1. Complexity (low/medium/high) +2. Dependencies on other tasks +3. Priority (P0-P3) +4. Best dev fit based on skills + +Format response as TOON: +{{complexity,dependencies,priority,dev_fit}}: +medium,TASK-abc123,P1,backend-dev-1 +""" + assessment = await self.think(prompt) + self.log.info( + "Task assessed", task_id=str(task_id), assessment=assessment[:100] + ) + self._pending_tasks.append(task_id) + + async def _phase_assign(self) -> None: + """ + ASSIGN phase: Match tasks to developers. + """ + self.log.debug("ASSIGN phase") + + while self._pending_tasks and self._cell_status.available_devs > 0: + task_id = self._pending_tasks.pop(0) + + # Find best dev + assignment = await self._find_best_dev(task_id) + if assignment: + await self._assign_task(assignment) + self._cell_status.available_devs -= 1 + + async def _phase_facilitate(self) -> None: + """ + FACILITATE phase: Help cell members. + + - Answer questions + - Clarify requirements + - Remove small blockers + - Unblock blocked tasks when blocker is resolved + """ + self.log.debug("FACILITATE phase") + + # Check for blocked tasks that may be resolvable + blocked_tasks = await self._get_blocked_tasks() + for task_id in blocked_tasks: + resolved = await self._check_blocker_resolved(task_id) + if resolved: + success = await self._unblock_task(task_id) + if success: + # Get session from task + _, session_id = await self._get_task_info(task_id) + await self.send_message( + session_id, + f"TASK-{str(task_id)[:8]} unblocked - blocker resolved", + message_type="action", + task_id=task_id, + ) + + # Check for pending questions in channel + questions = await self._get_pending_questions() + + for question_data in questions: + question_content = question_data.get("content", "") + task_id_raw = question_data.get("task_id") + session_id = question_data.get("session_id") + + # Use TOON for token-efficient context encoding + question_context = self.format_context_labeled( + "Cell Question", + {"question": question_content, "cell": self.cell_name}, + ) + + prompt = f"""A cell member needs help: + +{question_context} + +As the Cell PM, provide: +1. Answer if you can +2. Or route to appropriate person +3. Or escalate if needed + +Be helpful and unblock the team. +""" + response = await self.think(prompt) + + # Send response with task_id for proper routing + if session_id: + # Parse task_id if it's a string + parsed_task_id: UUID | None = None + if task_id_raw: + parsed_task_id = ( + UUID(task_id_raw) + if isinstance(task_id_raw, str) + else task_id_raw + ) + await self.send_message( + UUID(session_id) if isinstance(session_id, str) else session_id, + response, + message_type="answer", + task_id=parsed_task_id, + ) + self.log.info( + "PM responded to question", + task_id=str(task_id_raw) if task_id_raw else None, + response_preview=response[:100], + ) + else: + self.log.info( + "PM response (no session - using channel)", + task_id=str(task_id_raw) if task_id_raw else None, + response_preview=response[:100], + ) + + async def _phase_escalate(self) -> None: + """ + ESCALATE phase: Handle issues beyond cell control. + """ + self.log.debug("ESCALATE phase") + + for escalation in self._pending_escalations: + # Notify Main PM + await self._notify_main_pm(escalation) + + self._pending_escalations.clear() + + async def _phase_track(self) -> None: + """ + TRACK phase: Monitor task progress. + """ + self.log.debug("TRACK phase") + + active_tasks = await self._get_active_tasks() + + for task_id in active_tasks: + progress = await self._check_task_progress(task_id) + if progress.get("at_risk"): + self._cell_status.concerns.append(f"Task {str(task_id)[:8]} at risk") + + async def _phase_report(self) -> None: + """ + REPORT phase: Status to Main PM. + """ + self.log.debug("REPORT phase") + + concerns = self._format_concerns() + report = f""" +## {self.cell_name} Status Report + +**Active Tasks**: {self._cell_status.active_tasks} +**Blocked Tasks**: {self._cell_status.blocked_tasks} +**Completed Today**: {self._cell_status.completed_today} +**Available Devs**: {self._cell_status.available_devs} + +**Concerns**: +{concerns} +""" + # Would send to #pm-all channel + self.log.info("Report generated", report_length=len(report)) + self._cell_status.concerns.clear() + + def _format_concerns(self) -> str: + """Format concerns for report.""" + if not self._cell_status.concerns: + return "- None" + return chr(10).join(f"- {c}" for c in self._cell_status.concerns) + + # ========================================================================= + # HELPER METHODS + # ========================================================================= + + async def _count_active_tasks(self) -> int: + """Count active tasks in cell.""" + try: + team_param = self.team.value if self.team else None + result = await self._api_call( + "GET", + "/tasks", + params={"status": "in_progress", "team": team_param}, + ) + return len(result.get("items", [])) + except Exception as e: + self.log.warning("Failed to count active tasks", error=str(e)) + return 0 + + async def _count_blocked_tasks(self) -> int: + """Count blocked tasks in cell.""" + try: + team_param = self.team.value if self.team else None + result = await self._api_call( + "GET", + "/tasks", + params={"status": "blocked", "team": team_param}, + ) + return len(result.get("items", [])) + except Exception as e: + self.log.warning("Failed to count blocked tasks", error=str(e)) + return 0 + + async def _count_available_devs(self) -> int: + """Count available developers.""" + try: + team_param = self.team.value if self.team else None + result = await self._api_call( + "GET", + "/agents", + params={"role": "developer", "status": "idle", "team": team_param}, + ) + return len(result.get("items", [])) + except Exception as e: + self.log.warning("Failed to count available devs", error=str(e)) + return 0 + + async def _get_unassigned_tasks(self) -> list[UUID]: + """Get tasks needing assignment.""" + try: + team_param = self.team.value if self.team else None + result = await self._api_call( + "GET", + "/tasks", + params={"status": "pending", "team": team_param, "assigned_to": None}, + ) + return [UUID(t["id"]) for t in result.get("items", [])] + except Exception as e: + self.log.warning("Failed to get unassigned tasks", error=str(e)) + return [] + + async def _find_best_dev(self, task_id: UUID) -> TaskAssignment | None: + """Find best developer for a task.""" + try: + # Get available devs + team_param = self.team.value if self.team else None + result = await self._api_call( + "GET", + "/agents", + params={"role": "developer", "status": "idle", "team": team_param}, + ) + agents = result.get("items", []) + if not agents: + return None + + # For now, assign to first available + agent = agents[0] + return TaskAssignment( + task_id=task_id, + agent_id=UUID(agent["id"]), + agent_name=agent.get("name", "Unknown"), + reason="First available developer", + ) + except Exception as e: + self.log.warning("Failed to find best dev", error=str(e)) + return None + + async def _assign_task(self, assignment: TaskAssignment) -> None: + """Assign a task to a developer.""" + try: + await self._api_call( + "PUT", + f"/tasks/{assignment.task_id}", + json={"assigned_to": str(assignment.agent_id)}, + ) + self.log.info( + "Task assigned", + task_id=str(assignment.task_id), + agent=assignment.agent_name, + ) + except Exception as e: + self.log.error("Failed to assign task", error=str(e)) + + async def _get_pending_questions(self) -> list[dict[str, Any]]: + """Get unanswered questions from channel. + + Returns full message info including task_id for routing. + """ + try: + result = await self._api_call( + "GET", + "/messages", + params={"message_type": "dialogue", "unanswered": True}, + ) + return [ + { + "content": m.get("content", ""), + "task_id": m.get("task_id"), + "session_id": m.get("session_id"), + "from_agent": m.get("from_agent"), + } + for m in result.get("items", []) + ] + except Exception as e: + self.log.warning("Failed to get pending questions", error=str(e)) + return [] + + async def _notify_main_pm(self, escalation: Escalation) -> None: + """ + Notify Main PM of escalation. + + Uses NotificationType.BLOCKER_ESCALATION to formally escalate the issue. + """ + # Build notification content + notification_type = NotificationType.BLOCKER_ESCALATION + task_ref = str(escalation.task_id)[:8] if escalation.task_id else "N/A" + subject = f"Escalation from {self.cell_name}: {escalation.issue[:50]}" + body = f""" +## Escalation from {self.cell_name} + +**Issue:** {escalation.issue} +**Severity:** {escalation.severity} +**Task:** {task_ref} + +**Proposed Solution:** +{escalation.proposed_solution or "No solution proposed"} + +Please review and provide guidance. +""" + + self.log.info( + "Escalation sent to Main PM", + subject=subject, + body_length=len(body), + notification_type=notification_type.value, + severity=escalation.severity, + ) + + async def _get_active_tasks(self) -> list[UUID]: + """Get all active tasks in cell.""" + try: + team_param = self.team.value if self.team else None + result = await self._api_call( + "GET", + "/tasks", + params={"status": "in_progress", "team": team_param}, + ) + return [UUID(t["id"]) for t in result.get("items", [])] + except Exception as e: + self.log.warning("Failed to get active tasks", error=str(e)) + return [] + + async def _get_blocked_tasks(self) -> list[UUID]: + """Get all blocked tasks in cell.""" + try: + team_param = self.team.value if self.team else None + result = await self._api_call( + "GET", + "/tasks", + params={"status": "blocked", "team": team_param}, + ) + return [UUID(t["id"]) for t in result.get("items", [])] + except Exception as e: + self.log.warning("Failed to get blocked tasks", error=str(e)) + return [] + + async def _check_blocker_resolved(self, task_id: UUID) -> bool: + """ + Check if a task's blocker has been resolved. + + This examines the blocker_reason and checks if conditions are met. + For subtask blockers, checks if all subtasks are complete. + """ + try: + result = await self._api_call("GET", f"/tasks/{task_id}") + blocker_reason = result.get("blocker_reason", "") + + # Check if subtasks are complete (common blocker) + subtasks = result.get("subtasks", []) + if subtasks: + all_complete = all(s.get("status") == "completed" for s in subtasks) + if all_complete: + return True + + # If no specific logic, return False (needs manual review) + return not blocker_reason # Resolved if reason was cleared + + except Exception as e: + self.log.warning("Failed to check blocker", error=str(e)) + return False + + async def _check_task_progress(self, task_id: UUID) -> dict[str, Any]: + """ + Check progress of a task. + + Returns risk assessment based on task status and time in state. + """ + try: + result = await self._api_call("GET", f"/tasks/{task_id}") + status = result.get("status", "") + risk_factors = [] + + # Blocked tasks are always at risk + if status == TaskStatus.BLOCKED.value: + risk_factors.append("Task is blocked") + + return { + "at_risk": len(risk_factors) > 0, + "status": status, + "risk_factors": risk_factors, + } + except Exception as e: + self.log.warning("Failed to check task progress", error=str(e)) + return { + "at_risk": False, + "status": TaskStatus.IN_PROGRESS.value, + "risk_factors": [], + } + + +class MainPMAgent(Agent, CyclicPhaseRunner[MainPMPhase]): + """ + Main PM agent that coordinates all cells. + + Workflow: + 1. OVERSEE - Monitor all cells + 2. RECEIVE - Get direction from Board, escalations from Cell PMs + 3. PRIORITIZE - Translate Board direction to cell priorities + 4. COORDINATE - Resolve cross-cell issues + 5. DISTRIBUTE - Push tasks/priorities to Cell PMs + 6. REPORT UP - Status to Board + 7. FACILITATE - All-hands coordination + """ + + def __init__(self, config: AgentConfig) -> None: + """Initialize Main PM agent.""" + super().__init__(config) + self._current_phase = MainPMPhase.OVERSEE + self._cell_statuses: dict[str, CellStatus] = {} + self._board_directives: list[str] = [] + self._cross_cell_issues: list[dict[str, Any]] = [] + + async def _initialize(self) -> None: + """Initialize Main PM-specific resources.""" + self.log.debug("Main PM agent initialized", agent_id=str(self.id)) + + async def _cleanup(self) -> None: + """Cleanup Main PM-specific resources.""" + self._cell_statuses.clear() + self._board_directives.clear() + self._cross_cell_issues.clear() + self.log.debug("Main PM agent cleanup complete", agent_id=str(self.id)) + + # ========================================================================= + # CYCLIC PHASE RUNNER IMPLEMENTATION + # ========================================================================= + + def _get_cyclic_phase_configs(self) -> list[CyclicPhaseConfig[MainPMPhase]]: + """Define the Main PM workflow phases.""" + return [ + CyclicPhaseConfig( + MainPMPhase.OVERSEE, + self._phase_oversee, + MainPMPhase.RECEIVE, + ), + CyclicPhaseConfig( + MainPMPhase.RECEIVE, + self._phase_receive, + MainPMPhase.PRIORITIZE, + ), + CyclicPhaseConfig( + MainPMPhase.PRIORITIZE, + self._phase_prioritize, + MainPMPhase.COORDINATE, + ), + CyclicPhaseConfig( + MainPMPhase.COORDINATE, + self._phase_coordinate, + MainPMPhase.DISTRIBUTE, + ), + CyclicPhaseConfig( + MainPMPhase.DISTRIBUTE, + self._phase_distribute, + MainPMPhase.REPORT_UP, + ), + CyclicPhaseConfig( + MainPMPhase.REPORT_UP, + self._phase_report_up, + MainPMPhase.FACILITATE, + ), + CyclicPhaseConfig( + MainPMPhase.FACILITATE, + self._phase_facilitate, + MainPMPhase.OVERSEE, # Cycle back + ), + ] + + # ========================================================================= + # LIFECYCLE IMPLEMENTATION + # ========================================================================= + + async def find_work(self) -> UUID | None: + """ + Find work for the Main PM. + + Priority: + 1. Paused tasks with all subtasks complete (ready for closure) + 2. Assigned tasks in progress + 3. Fall back to cyclic management duties (self.id) + """ + # Check for paused tasks ready for closure + ready_task = await self._find_paused_task_ready_for_closure() + if ready_task: + self.log.info( + "Found paused task ready for closure", task_id=str(ready_task) + ) + return ready_task + + # Check for assigned in-progress tasks + assigned_task = await self._find_assigned_task() + if assigned_task: + self.log.info("Found assigned task", task_id=str(assigned_task)) + return assigned_task + + # Fall back to cyclic management duties + return self.id + + async def _find_paused_task_ready_for_closure(self) -> UUID | None: + """ + Find own paused tasks where all subtasks are complete. + + This is the key PM workflow: delegate subtasks, pause, get respawned + when subtasks complete, then review and close. + """ + try: + # Get my paused tasks + result = await self._api_call( + "GET", + "/tasks", + params={"status": "paused", "assigned_to": str(self.id)}, + ) + paused_tasks = ( + result.get("items", result) if isinstance(result, dict) else result + ) + + for task in paused_tasks: + task_id = task.get("id") + if not task_id: + continue + + # Check if this task has subtasks + subtasks_result = await self._api_call( + "GET", + "/tasks", + params={"parent_task_id": task_id}, + ) + subtasks = ( + subtasks_result.get("items", subtasks_result) + if isinstance(subtasks_result, dict) + else subtasks_result + ) + + if not subtasks: + continue # No subtasks - not a delegation task + + # Check if ALL subtasks are complete + all_complete = all( + s.get("status") in ("completed", "cancelled") for s in subtasks + ) + + if all_complete: + return UUID(task_id) if isinstance(task_id, str) else task_id + + return None + + except Exception as e: + self.log.warning( + "Failed to find paused tasks ready for closure", error=str(e) + ) + return None + + async def _find_assigned_task(self) -> UUID | None: + """Find tasks assigned to this PM that are in progress.""" + try: + result = await self._api_call( + "GET", + "/tasks", + params={"status": "in_progress", "assigned_to": str(self.id)}, + ) + tasks = result.get("items", result) if isinstance(result, dict) else result + return UUID(tasks[0]["id"]) if tasks else None + except Exception as e: + self.log.warning("Failed to find assigned task", error=str(e)) + return None + + async def execute_task(self, task_id: UUID) -> bool: + """ + Execute Main PM work. + + Two modes: + 1. task_id == self.id: Run cyclic coordination duties + 2. task_id is real task: Work on specific task (CLAIM → PLAN → START → ...) + + Returns True when task-specific work is complete, False for cyclic duties. + """ + # Cyclic coordination duties (no specific task) + if task_id == self.id: + error = await self._run_phase_cycle() + if error: + self.log.error( + "Error in Main PM phase", + phase=self._current_phase.value, + error=error, + ) + return False # Never complete - continuous duty + + # Task-specific work - delegate to task workflow + return await self._execute_main_pm_task(task_id) + + async def _execute_main_pm_task(self, task_id: UUID) -> bool: + """ + Execute Main PM workflow on a specific task. + + Main PM Workflow: CLAIM → PLAN → START → DISTRIBUTE → PAUSE → COMPLETE + + Returns True when Main PM work is done (distributed or completed). + """ + try: + task = await self._api_call("GET", f"/tasks/{task_id}") + status = task.get("status") + return await self._handle_main_pm_task_status(task_id, task, status) + except Exception as e: + self.log.error( + "Error in Main PM task execution", + task_id=str(task_id), + error=str(e), + ) + return False + + async def _handle_main_pm_task_status( + self, task_id: UUID, task: dict[str, Any], status: str | None + ) -> bool: + """Handle Main PM task based on current status.""" + if status == "pending": + await self._mark_claimed(task_id) + self.log.info("Main PM claimed task", task_id=str(task_id)) + return False + + if status == "claimed": + return await self._handle_main_pm_claimed(task_id, task) + + if status == "in_progress": + return await self._handle_main_pm_in_progress(task_id, task) + + if status == "paused": + await self._mark_completed(task_id) + self.log.info("Main PM completed task", task_id=str(task_id)) + return True + + return False + + def _get_team_channel(self, team: str) -> str: + """Get the channel slug for a team.""" + channel_map = { + "backend": "backend-cell", + "frontend": "frontend-cell", + "ux_ui": "uxui-cell", + } + return channel_map.get(team, "dev-all") + + async def _handle_main_pm_claimed( + self, task_id: UUID, task: dict[str, Any] + ) -> bool: + """Handle claimed task - plan then start.""" + if not task.get("plan"): + plan = await self._create_main_pm_plan(task) + await self._api_call("PATCH", f"/tasks/{task_id}", json={"plan": plan}) + self.log.info("Main PM planned task", task_id=str(task_id)) + return False + + await self._mark_in_progress(task_id) + self.log.info("Main PM started task", task_id=str(task_id)) + return False + + async def _handle_main_pm_in_progress( + self, task_id: UUID, task: dict[str, Any] + ) -> bool: + """Handle in_progress task - distribute and pause.""" + distributed = await self._distribute_to_cells(task_id, task) + if distributed: + remaining = ["Monitor cell progress", "Close initiative"] + await self._api_call( + "POST", + f"/tasks/{task_id}/pause", + json={ + "reason": "Awaiting cell completion", + "checkpoint_summary": "Distributed to Cell PMs", + "remaining_work": remaining, + }, + ) + self.log.info("Main PM distributed and paused", task_id=str(task_id)) + return True + + async def _create_main_pm_plan(self, task: dict[str, Any]) -> dict[str, Any]: + """Create a Main PM coordination plan for an initiative.""" + return { + "approach": "Coordinate across cells to deliver initiative", + "steps": [ + "Analyze initiative requirements", + "Identify cell responsibilities", + "Create tasks for Cell PMs", + "Set up cross-cell sessions", + "Monitor and coordinate", + ], + "risks": task.get("acceptance_criteria", [])[:2], + "estimated_sessions": 2, + } + + async def _distribute_to_cells(self, task_id: UUID, task: dict[str, Any]) -> bool: + """Distribute initiative to appropriate Cell PMs.""" + title = task.get("title", "").lower() + description = task.get("description", "").lower() + content = title + description + + cells_needed = self._determine_cells_needed(content) + + for team, pm_slug in cells_needed: + await self._create_cell_task(task_id, task, team, pm_slug) + + return len(cells_needed) > 0 + + def _determine_cells_needed(self, content: str) -> list[tuple[str, str]]: + """Determine which cells are needed based on content keywords.""" + cells = [] + backend_kw = ["api", "backend", "database", "server"] + frontend_kw = ["ui", "frontend", "component", "page"] + ux_kw = ["design", "ux", "figma", "mockup"] + + if any(kw in content for kw in backend_kw): + cells.append(("backend", "be-pm")) + if any(kw in content for kw in frontend_kw): + cells.append(("frontend", "fe-pm")) + if any(kw in content for kw in ux_kw): + cells.append(("ux_ui", "ux-pm")) + + return cells if cells else [("backend", "be-pm")] + + async def _create_cell_task( + self, parent_id: UUID, task: dict[str, Any], team: str, pm_slug: str + ) -> None: + """Create a task for a Cell PM. + + Follows blueprint workflow: + CREATE (backlog) → GROUP → SESSION → ACTIVATE (pending) → NOTIFY + """ + # Step 1: Create task with status "backlog" + task_resp = await self._api_call( + "POST", + "/tasks", + json={ + "title": f"[{team.upper()}] {task.get('title', 'Task')}", + "description": task.get("description", ""), + "team": team, + "acceptance_criteria": task.get("acceptance_criteria", []), + "parent_task_id": str(parent_id), + "assigned_to": pm_slug, + "status": "backlog", # Backlog until session is ready + }, + ) + cell_task_id = task_resp.get("id") + if not cell_task_id: + self.log.error("Failed to create cell task", parent_id=str(parent_id)) + return + + self.log.info( + "Main PM created cell task (backlog)", + cell_task_id=cell_task_id, + parent_task_id=str(parent_id), + team=team, + assigned_to=pm_slug, + ) + + # Step 2: Create group if needed (cross-cell initiatives) + channel_slug = self._get_team_channel(team) + + # Step 3: Create session for the cell task + try: + await self._api_call( + "POST", + "/sessions/for-tasks", + json={ + "task_ids": [cell_task_id], + "channel_slug": channel_slug, + "scope": f"Cell work for {task.get('title', 'initiative')}", + "relationship_type": "implements", + }, + ) + self.log.info("Session created for cell task", cell_task_id=cell_task_id) + except Exception as e: + self.log.warning( + "Session creation failed, activating anyway", + cell_task_id=cell_task_id, + error=str(e), + ) + + # Step 4: Activate task (changes status to pending) + try: + await self._api_call("POST", f"/tasks/{cell_task_id}/activate") + self.log.info("Cell task activated", cell_task_id=cell_task_id) + except Exception as e: + self.log.error( + "Failed to activate cell task", + cell_task_id=cell_task_id, + error=str(e), + ) + return + + # Step 5: Notify Cell PM + try: + await self._api_call( + "POST", + "/notifications", + json={ + "type": "task_assigned", + "priority": "normal", + "to_agents": [pm_slug], + "subject": f"New initiative: {task.get('title', 'Task')}", + "body": ( + f"A new initiative has been assigned to your cell.\n\n" + f"Task ID: {cell_task_id}\n" + f"Title: {task.get('title', 'Task')}\n\n" + f"Please triage and delegate to your team." + ), + "related_task_id": cell_task_id, + "requires_ack": True, + }, + ) + except Exception as e: + self.log.warning( + "Failed to notify Cell PM (task still assigned)", + error=str(e), + ) + + # ========================================================================= + # MAIN PM PHASES + # ========================================================================= + + async def _phase_oversee(self) -> None: + """OVERSEE phase: Monitor all cells.""" + self.log.debug("OVERSEE phase") + + # Collect status from all cells + for cell_name in ["backend-cell", "frontend-cell", "uxui-cell"]: + status = await self._get_cell_status(cell_name) + self._cell_statuses[cell_name] = status + + # Look for cross-cell issues + self._cross_cell_issues = await self._detect_cross_cell_issues() + + async def _phase_receive(self) -> None: + """RECEIVE phase: Get direction and escalations.""" + self.log.debug("RECEIVE phase") + + # Check for Board directives + self._board_directives = await self._get_board_directives() + + # Check for Cell PM escalations + escalations = await self._get_cell_pm_escalations() + for esc in escalations: + self.log.info("Received escalation", issue=esc.get("issue")) + + async def _phase_prioritize(self) -> None: + """PRIORITIZE phase: Set cross-cell priorities.""" + self.log.debug("PRIORITIZE phase") + + if self._board_directives: + # Build status data for TOON encoding + cell_status_data = { + name: {"active": s.active_tasks, "blocked": s.blocked_tasks} + for name, s in self._cell_statuses.items() + } + + # Use TOON for token-efficient context encoding + priority_context = self.format_context_labeled( + "Prioritization Context", + { + "directives": self._board_directives, + "cell_status": cell_status_data, + }, + ) + + prompt = f"""Translate these Board directives into cell priorities: + +{priority_context} + +Format response as TOON tabular: +[N,]{{cell,priority,task_description}}: +backend-cell,P0,Implement critical auth fix +frontend-cell,P1,Update dashboard layout +""" + priorities = await self.think(prompt) + self.log.info("Priorities set", priorities=priorities[:200]) + + async def _phase_coordinate(self) -> None: + """COORDINATE phase: Resolve cross-cell issues.""" + self.log.debug("COORDINATE phase") + + for issue in self._cross_cell_issues: + # Use TOON for token-efficient context encoding + issue_context = self.format_context_labeled( + "Cross-Cell Issue", + { + "description": issue.get("description"), + "cells": issue.get("cells"), + "task_id": issue.get("task_id"), + }, + ) + + prompt = f"""Resolve this cross-cell issue: + +{issue_context} + +Propose a resolution that unblocks all parties. +""" + resolution = await self.think(prompt) + await self._apply_resolution(issue, resolution) + + self._cross_cell_issues.clear() + + async def _phase_distribute(self) -> None: + """DISTRIBUTE phase: Push priorities to Cell PMs.""" + self.log.debug("DISTRIBUTE phase") + + for directive in self._board_directives: + # Determine which cell(s) need this + cell = self._route_directive(directive) + if cell: + await self._notify_cell_pm(cell, directive) + + self._board_directives.clear() + + async def _phase_report_up(self) -> None: + """REPORT UP phase: Status to Board.""" + self.log.debug("REPORT UP phase") + + report = """ +## Organization Status Report + +### Cell Summary +""" + for cell_name, status in self._cell_statuses.items(): + report += f""" +**{cell_name}**: +- Active: {status.active_tasks} +- Blocked: {status.blocked_tasks} +- Available: {status.available_devs} +""" + + # Would send to #main-pm-board + self.log.info("Board report generated", report_length=len(report)) + + async def _phase_facilitate(self) -> None: + """FACILITATE phase: All-hands coordination.""" + self.log.debug("FACILITATE phase") + + # Check for all-hands items + # Process improvements, announcements, etc. + + # ========================================================================= + # HELPER METHODS + # ========================================================================= + + async def _get_cell_status(self, cell_name: str) -> CellStatus: + """Get status of a cell.""" + try: + # Get task counts per status for this cell + team = cell_name.replace("-cell", "") + active_result = await self._api_call( + "GET", + "/tasks", + params={"status": "in_progress", "team": team}, + ) + blocked_result = await self._api_call( + "GET", + "/tasks", + params={"status": "blocked", "team": team}, + ) + devs_result = await self._api_call( + "GET", + "/agents", + params={"role": "developer", "status": "idle", "team": team}, + ) + return CellStatus( + name=cell_name, + active_tasks=len(active_result.get("items", [])), + blocked_tasks=len(blocked_result.get("items", [])), + available_devs=len(devs_result.get("items", [])), + ) + except Exception as e: + self.log.warning("Failed to get cell status", cell=cell_name, error=str(e)) + return CellStatus(name=cell_name) + + async def _detect_cross_cell_issues(self) -> list[dict[str, Any]]: + """Detect cross-cell dependencies and issues.""" + try: + # Look for tasks blocked by other cells + result = await self._api_call( + "GET", + "/tasks", + params={"status": "blocked"}, + ) + issues = [] + for task in result.get("items", []): + blocker = task.get("blocker_reason", "") + if "frontend" in blocker.lower() or "backend" in blocker.lower(): + issues.append( + { + "description": f"Cross-cell blocker: {blocker}", + "cells": [task.get("team", "unknown")], + "task_id": task.get("id"), + } + ) + return issues + except Exception as e: + self.log.warning("Failed to detect cross-cell issues", error=str(e)) + return [] + + async def _get_board_directives(self) -> list[str]: + """Get directives from Board.""" + try: + result = await self._api_call( + "GET", + "/messages", + params={"channel": "main-pm-board", "message_type": "action"}, + ) + return [m.get("content", "") for m in result.get("items", [])] + except Exception as e: + self.log.warning("Failed to get board directives", error=str(e)) + return [] + + async def _get_cell_pm_escalations(self) -> list[dict[str, Any]]: + """Get escalations from Cell PMs.""" + try: + result = await self._api_call( + "GET", + "/notifications", + params={"type": "escalation", "status": "pending"}, + ) + items: list[dict[str, Any]] = result.get("items", []) + return items + except Exception as e: + self.log.warning("Failed to get escalations", error=str(e)) + return [] + + async def _apply_resolution( + self, + issue: dict[str, Any], + resolution: str, + ) -> None: + """Apply a cross-cell resolution.""" + self.log.info( + "Resolution applied", + issue=issue.get("description"), + resolution_length=len(resolution), + ) + + def _route_directive(self, directive: str) -> str | None: + """Route a directive to appropriate cell.""" + directive_lower = directive.lower() + if "backend" in directive_lower or "api" in directive_lower: + return "backend-cell" + elif "frontend" in directive_lower or "ui" in directive_lower: + return "frontend-cell" + elif "ux" in directive_lower or "design" in directive_lower: + return "uxui-cell" + return None + + async def _notify_cell_pm(self, cell: str, directive: str) -> None: + """Notify a Cell PM of a directive.""" + self.log.info("Directive sent", cell=cell, directive=directive[:50]) diff --git a/roboco/agents/qa.py b/roboco/agents/qa.py new file mode 100644 index 00000000..35fa4fd7 --- /dev/null +++ b/roboco/agents/qa.py @@ -0,0 +1,492 @@ +""" +QA Agent + +Implementation of the QA workflow from the blueprint. +Handles review lifecycle: + MONITOR → RECEIVE → UNDERSTAND → TEST → VERDICT → DOCUMENT → RETURN +""" + +from datetime import UTC, datetime +from uuid import UUID + +import structlog +from roboco.agents.base import Agent, AgentConfig +from roboco.agents.mixins import PhaseConfig, PhaseEngine +from roboco.models.agents import ( + QATaskPhase, + ReviewContext, + TestCase, + TestResult, +) + +logger = structlog.get_logger() + + +class QAAgent(Agent, PhaseEngine[QATaskPhase, ReviewContext]): + """ + QA agent that follows the QA Lifecycle. + + Workflow: + 1. MONITOR - Watch cell channel, track tasks approaching completion + 2. RECEIVE - Dev flags ready, claim review task + 3. UNDERSTAND - Read requirements, dev notes, commits + 4. TEST - Execute test scenarios, edge cases + 5. VERDICT - PASS or FAIL with clear feedback + 6. DOCUMENT - Add QA notes, test coverage + 7. RETURN - Back to monitoring + """ + + def __init__(self, config: AgentConfig) -> None: + """Initialize QA agent.""" + super().__init__(config) + self._review_context: ReviewContext | None = None + self._cell_channel_id: UUID | None = None + self._pending_reviews: list[UUID] = [] + + async def _initialize(self) -> None: + """Initialize QA-specific resources.""" + self.log.debug("QA agent initialized", agent_id=str(self.id)) + + async def _cleanup(self) -> None: + """Cleanup QA-specific resources.""" + self._review_context = None + self._pending_reviews.clear() + self.log.debug("QA agent cleanup complete", agent_id=str(self.id)) + + # ========================================================================= + # PHASE ENGINE IMPLEMENTATION + # ========================================================================= + + def _get_phase_configs(self) -> list[PhaseConfig[QATaskPhase]]: + """Define the QA workflow phases.""" + return [ + PhaseConfig( + QATaskPhase.RECEIVE, + self._phase_receive, + next_phase=QATaskPhase.UNDERSTAND, + ), + PhaseConfig( + QATaskPhase.UNDERSTAND, + self._phase_understand, + next_phase=QATaskPhase.TEST, + ), + PhaseConfig( + QATaskPhase.TEST, + self._phase_test, + next_phase=QATaskPhase.VERDICT, + requires_completion=True, + ), + PhaseConfig( + QATaskPhase.VERDICT, + self._phase_verdict, + next_phase=QATaskPhase.DOCUMENT, + ), + PhaseConfig( + QATaskPhase.DOCUMENT, + self._phase_document, + next_phase=QATaskPhase.RETURN, + ), + PhaseConfig( + QATaskPhase.RETURN, + self._phase_return, + next_phase=None, # Terminal + ), + ] + + def _get_current_phase(self, ctx: ReviewContext) -> QATaskPhase: + """Get the current phase from context.""" + return ctx.phase + + def _set_current_phase(self, ctx: ReviewContext, phase: QATaskPhase) -> None: + """Set the current phase in context.""" + ctx.phase = phase + + # ========================================================================= + # LIFECYCLE IMPLEMENTATION + # ========================================================================= + + async def find_work(self) -> UUID | None: + """ + MONITOR phase: Watch for tasks ready for review. + + - Check for tasks flagged as awaiting_qa + - Check for PM notifications + """ + self.log.info("Monitoring for reviews") + + # Check pending reviews queue + if self._pending_reviews: + return self._pending_reviews.pop(0) + + # Query for tasks awaiting QA + task_id = await self._find_awaiting_qa() + if task_id: + return task_id + + return None + + async def execute_task(self, task_id: UUID) -> bool: + """ + Execute review through QA lifecycle phases. + + Returns True when review is complete. + """ + if self._review_context is None or self._review_context.task_id != task_id: + title, session_id = await self._get_task_info(task_id) + self._review_context = ReviewContext( + task_id=task_id, + title=title, + session_id=session_id, + ) + + ctx = self._review_context + + try: + result = await self._run_phase_engine(ctx) + + if result.error: + self.log.error( + "Error in review phase", + phase=ctx.phase.value, + error=result.error, + ) + ctx.findings.append(f"Error during review: {result.error}") + return False + + if result.completed: + self._review_context = None + return True + + return False + + except Exception as e: + self.log.error( + "Error in review phase", + phase=ctx.phase.value, + error=str(e), + ) + ctx.findings.append(f"Error during review: {e}") + return False + + # ========================================================================= + # PHASE IMPLEMENTATIONS + # ========================================================================= + + async def _phase_receive(self, ctx: ReviewContext) -> None: + """ + RECEIVE phase: Claim the review task. + + - Claim task via /claim endpoint + - Acknowledge receipt + - Announce review started + """ + self.log.info("RECEIVE phase", task_id=str(ctx.task_id)) + + # CLAIM: Transition from awaiting_qa to claimed + await self._mark_claimed(ctx.task_id) + + await self.send_message( + ctx.session_id, + f"Starting review of TASK-{str(ctx.task_id)[:8]}: {ctx.title}", + message_type="action", + task_id=ctx.task_id, + ) + + ctx.notes.append(f"[{datetime.now(UTC).isoformat()}] Review started") + + async def _phase_understand(self, ctx: ReviewContext) -> None: + """ + UNDERSTAND phase: Read requirements and dev notes. + + - Read task requirements and acceptance criteria + - Read dev's handoff notes (from task's dev_notes field) + - Review commits + - Check conversation history + - Read developer's journal entries for this task (if needed) + """ + self.log.info("UNDERSTAND phase", task_id=str(ctx.task_id)) + + # Read task context + requirements = await self._read_task_requirements(ctx.task_id) + dev_notes = await self._read_dev_notes(ctx.task_id) + commits = await self._get_task_commits_formatted(ctx.task_id) + + # Read developer journal entries for this task (cell members can read) + dev_journal = await self._read_team_journal_for_task(ctx.task_id) + + # Use TOON for token-efficient context encoding + task_context = self._format_review_context( + ctx.title, requirements, dev_notes, commits + ) + + # Include journal context if available + journal_context = "" + if dev_journal: + journal_context = f"\n\nDeveloper Journal Entries:\n{dev_journal}" + + prompt = f"""You are a QA engineer reviewing a completed task. + +{task_context}{journal_context} + +Based on this, create test cases to verify the implementation. + +Focus on: +- Acceptance criteria verification +- Edge cases +- Integration points +- Error handling + +If acceptance criteria mentions journaling requirements, verify them against +the developer journal entries provided above. + +Format response as TOON tabular: +[N,]{{name,description,steps,expected}}: +Acceptance Criteria,Verify all criteria met,Review implementation|Check each criterion,All criteria satisfied +""" # noqa: E501 + _response = await self.think(prompt) + + # Create test cases (simplified parsing) + ctx.test_cases = [ + TestCase( + name="Acceptance Criteria", + description="Verify all acceptance criteria are met", + steps=["Review implementation", "Check each criterion"], + expected="All criteria satisfied", + ), + TestCase( + name="Edge Cases", + description="Test edge cases and error handling", + steps=["Test with invalid input", "Test boundary conditions"], + expected="Graceful handling of edge cases", + ), + TestCase( + name="Integration", + description="Verify integration with existing code", + steps=["Run integration tests", "Check API compatibility"], + expected="No breaking changes", + ), + ] + + # PLAN: Save test plan to task API (required before start) + plan_data = { + "approach": f"QA review of {ctx.title}", + "sub_tasks": [ + { + "id": f"test-{i}", + "title": tc.name, + "description": tc.description, + "completed": False, + "order": i, + } + for i, tc in enumerate(ctx.test_cases) + ], + "risks": [], + } + await self._api_call("PATCH", f"/tasks/{ctx.task_id}", json={"plan": plan_data}) + + ts = datetime.now(UTC).isoformat() + ctx.notes.append(f"[{ts}] Created {len(ctx.test_cases)} test cases") + + async def _phase_test(self, ctx: ReviewContext) -> bool: + """ + TEST phase: Execute test scenarios. + + - START: Transition to in_progress on first test + - Run through each test case + - Document findings + + Returns True when all tests complete. + """ + self.log.info( + "TEST phase", + task_id=str(ctx.task_id), + test=ctx.current_test, + total=len(ctx.test_cases), + ) + + # START: Transition to in_progress on first test + if ctx.current_test == 0: + await self._mark_in_progress(ctx.task_id) + self.log.info("QA review started (in_progress)", task_id=str(ctx.task_id)) + + if ctx.current_test >= len(ctx.test_cases): + return True + + test_case = ctx.test_cases[ctx.current_test] + + # Use TOON for token-efficient context encoding + test_context = self._format_test_context( + test_case.name, + test_case.description, + test_case.steps, + test_case.expected, + ) + + prompt = f"""Execute this test case: + +{test_context} + +Simulate executing this test and provide results. + +Format response as TOON: +{{result,actual,notes}}: +PASS,All criteria verified successfully,No issues found +""" + response = await self.think(prompt) + + # Parse result (simplified) + if "PASS" in response.upper(): + test_case.result = TestResult.PASS + else: + test_case.result = TestResult.FAIL + ctx.findings.append(f"FAIL: {test_case.name}") + + test_case.actual = response + ctx.current_test += 1 + + # Progress update + progress = f"{ctx.current_test}/{len(ctx.test_cases)}" + result_str = test_case.result.value.upper() + task_ref = str(ctx.task_id)[:8] + msg = f"TASK-{task_ref} test {progress}: {test_case.name} - {result_str}" + await self.send_message( + ctx.session_id, + msg, + message_type="action", + task_id=ctx.task_id, + ) + + return ctx.current_test >= len(ctx.test_cases) + + async def _phase_verdict(self, ctx: ReviewContext) -> None: + """ + VERDICT phase: Determine overall pass/fail. + + - Analyze all test results + - Communicate clear verdict + - If fail, provide specific feedback + """ + self.log.info("VERDICT phase", task_id=str(ctx.task_id)) + + # Determine verdict + failed_tests = [t for t in ctx.test_cases if t.result == TestResult.FAIL] + + if failed_tests: + ctx.verdict = TestResult.FAIL + + # Communicate failure with specifics + failure_summary = "\n".join( + [f"- {t.name}: {t.actual or 'No details'}" for t in failed_tests] + ) + + await self.send_message( + ctx.session_id, + f"TASK-{str(ctx.task_id)[:8]} QA FAILED\n\n" + f"Issues found:\n{failure_summary}\n\n" + f"Task returned to developer for fixes.", + message_type="decision", + task_id=ctx.task_id, + ) + + # Use proper QA fail endpoint (handles notes, status, reassignment) + issue_list = [t.actual or t.name for t in failed_tests] + await self._qa_fail( + ctx.task_id, + "Found issues that need fixing before approval.", + issue_list, + ) + + else: + ctx.verdict = TestResult.PASS + + await self.send_message( + ctx.session_id, + f"TASK-{str(ctx.task_id)[:8]} QA APPROVED\n\n" + f"All {len(ctx.test_cases)} tests passed.\n" + f"Ready for documentation.", + message_type="decision", + task_id=ctx.task_id, + ) + + # Use proper QA pass endpoint (handles notes, status) + await self._qa_pass( + ctx.task_id, + f"All {len(ctx.test_cases)} tests passed. Ready for documentation.", + ) + + ctx.notes.append( + f"[{datetime.now(UTC).isoformat()}] Verdict: {ctx.verdict.value.upper()}" + ) + + async def _phase_document(self, ctx: ReviewContext) -> None: + """ + DOCUMENT phase: Add QA notes to task. + + - Document test coverage + - Add handoff notes for documenter + """ + self.log.info("DOCUMENT phase", task_id=str(ctx.task_id)) + + # Generate QA report + test_summary = "\n".join( + [ + f"- {t.name}: {t.result.value.upper() if t.result else 'NOT RUN'}" + for t in ctx.test_cases + ] + ) + + qa_report = f""" +## QA Review Summary + +**Task**: {ctx.title} +**Verdict**: {ctx.verdict.value.upper() if ctx.verdict else "UNKNOWN"} +**Reviewed**: {datetime.now(UTC).isoformat()} + +### Tests Executed + +{test_summary} + +### Findings + +{chr(10).join(ctx.findings) if ctx.findings else "No issues found"} + +### Notes + +{chr(10).join(ctx.notes)} +""" + + # Would save to task record + self.log.info("QA report generated", report_length=len(qa_report)) + + ctx.notes.append(f"[{datetime.now(UTC).isoformat()}] QA documentation complete") + + async def _phase_return(self, ctx: ReviewContext) -> None: + """ + RETURN phase: Clean up and return to monitoring. + """ + self.log.info("RETURN phase", task_id=str(ctx.task_id)) + # Context will be cleared by execute_task on completion + + # ========================================================================= + # HELPER METHODS + # ========================================================================= + + async def _find_awaiting_qa(self) -> UUID | None: + """Find tasks awaiting QA review.""" + try: + team_param = self.team.value if self.team else None + result = await self._api_call( + "GET", + "/tasks", + params={"status": "awaiting_qa", "team": team_param}, + ) + tasks = result.get("items", []) + return UUID(tasks[0]["id"]) if tasks else None + except Exception as e: + self.log.warning("Failed to find awaiting QA task", error=str(e)) + return None + + async def _get_task_commits_formatted(self, task_id: UUID) -> str: + """Get commits for the task as formatted string.""" + commits = await self._get_task_commits(task_id) + return "\n".join(commits) if commits else "No commits recorded" diff --git a/roboco/mcp/tasks/handlers/claim.py b/roboco/mcp/tasks/handlers/claim.py index 51d5d668..1e3ee0b2 100644 --- a/roboco/mcp/tasks/handlers/claim.py +++ b/roboco/mcp/tasks/handlers/claim.py @@ -12,26 +12,50 @@ from roboco.mcp.tasks.handlers._helpers import ( check_blocking_tasks, fetch_task_or_error, get_project_context, + resolve_agent_uuid_cached, validate_task_claimable, ) from roboco.mcp.utils import ApiClient, format_error_response -async def _check_active_tasks(client: ApiClient) -> dict[str, Any] | None: +async def _check_active_tasks( + client: ApiClient, exclude_task_id: str | None = None +) -> dict[str, Any] | None: """Check for blocking tasks. Returns error or None. - Note: Paused tasks no longer block claiming. Agents can verify why - a task is paused (via roboco_task_scan) and decide to resume it - or claim new work if it's legitimately waiting on something. + Args: + client: API client + exclude_task_id: Task ID to exclude from blocking check. Used when + the agent is claiming a task already assigned to them. """ active_resp = await client.get("/tasks/my") if not active_resp.ok: return None active_tasks = active_resp.json() - # Only block on in_progress tasks, not paused ones + + # Exclude the task being claimed if specified + if exclude_task_id: + active_tasks = [t for t in active_tasks if str(t.get("id")) != exclude_task_id] + return check_blocking_tasks(active_tasks) +async def _is_pre_assigned_to_agent( + task: dict[str, Any], agent_id: str, client: ApiClient +) -> bool: + """Check if task is pre-assigned to this agent (PM assigned before claim).""" + assigned_to = task.get("assigned_to") + if not assigned_to: + return False + + # Task must be pending (not yet claimed) + if task.get("status") != "pending": + return False + + agent_uuid = await resolve_agent_uuid_cached(agent_id, client) + return agent_uuid is not None and str(assigned_to) == agent_uuid + + async def _execute_claim( client: ApiClient, task_id: str, agent_id: str ) -> tuple[dict[str, Any] | None, dict[str, Any] | None]: @@ -51,19 +75,36 @@ async def _execute_claim( async def handle_task_claim( client: ApiClient, task_id: str, agent_id: str ) -> dict[str, Any]: - """Handle task claiming.""" - if error := await _check_active_tasks(client): - return error + """Handle task claiming. + Flow: + 1. Fetch the task first + 2. Check if it's pre-assigned to this agent (PM assigned directly) + 3. If pre-assigned, skip blocking check for THIS task + 4. Otherwise, run full blocking check + 5. Validate task is claimable for this role + 6. Execute claim + """ + # Fetch task first - we need to check if it's pre-assigned task, error = await fetch_task_or_error(client, task_id) if error: return error assert task is not None + # Check if this task is pre-assigned to the agent + is_pre_assigned = await _is_pre_assigned_to_agent(task, agent_id, client) + + # Check for blocking tasks (exclude this task if pre-assigned) + exclude_id = task_id if is_pre_assigned else None + if error := await _check_active_tasks(client, exclude_task_id=exclude_id): + return error + + # Validate task can be claimed by this role agent_role = get_agent_role(agent_id) if error := await validate_task_claimable(task, agent_role, agent_id, client): return error + # Execute the claim claimed_task, error = await _execute_claim(client, task_id, agent_id) if error: return error