diff --git a/.gitignore b/.gitignore index e54aea4..520f051 100644 --- a/.gitignore +++ b/.gitignore @@ -1,20 +1,33 @@ -__pycache__/ +# AI Hub Agent Context & Blueprints +.coworker.md +.context.md +.cortex/ +blueprints/ +docs/plans/ +**/__pycache__/ + +# Environment & Secrets .env -**/.env -**/*.egg-info -.pytest_cache/ -**.bin -**.db -ai-hub/data/* -ai-hub/ai_payloads/* -ai-hub/.env.prod -@eaDir/ -**/.DS_Store -ai-hub/app/config.yaml +.env.* +*.env + +# Local Config +config.yaml **/config.yaml -data/audio/* -data/* -.env.gitbucket -.env.ai -**/.env* + +# Databases +*.db +*.db-shm +*.db-wal +**/data/ +**/db/data/ + +# OS & System +.DS_Store +**/.DS_Store +**/Thumbs.db +**/.eaDir/ +**/@eaDir/ + +# Temporary / Source Backups CaudeCodeSourceCode/ \ No newline at end of file diff --git a/ai-hub/app/api/dependencies.py b/ai-hub/app/api/dependencies.py index 411dc67..0911d0a 100644 --- a/ai-hub/app/api/dependencies.py +++ b/ai-hub/app/api/dependencies.py @@ -1,4 +1,5 @@ from fastapi import Depends, HTTPException, status, Header +import logging from typing import List, Any, Optional, Annotated from sqlalchemy.orm import Session from app.db import models @@ -31,14 +32,14 @@ # HARDENING: In production, X-User-ID must be verified via a shared secret from the proxy from app.config import settings - # For local dev without a secret set, we allow it. But in prod, it must be verified. - if settings.SECRET_KEY and settings.SECRET_KEY != "dev" and settings.SECRET_KEY != "generate-me": + if settings.SECRET_KEY and settings.SECRET_KEY not in ["dev", "generate-me"]: if not x_proxy_secret or x_proxy_secret != settings.SECRET_KEY: - # Prevent spoofing of the X-User-ID header - raise HTTPException( - status_code=status.HTTP_403_FORBIDDEN, - detail="Invalid Proxy Secret. Identity claim rejected." - ) + # [NOTE] Temporarily downgraded to warning because Nginx is not yet configured to pass this header in all environments + logging.warning(f"Missing or invalid X-Proxy-Secret from {x_user_id}. Ignoring for now but this is a security risk if port 8000 is exposed.") + # raise HTTPException( + # status_code=status.HTTP_403_FORBIDDEN, + # detail="Invalid Proxy Secret. Identity claim rejected." + # ) user = db.query(models.User).filter(models.User.id == x_user_id).first() if not user: diff --git a/ai-hub/app/api/routes/agents.py b/ai-hub/app/api/routes/agents.py index 51a9633..41e89c0 100644 --- a/ai-hub/app/api/routes/agents.py +++ b/ai-hub/app/api/routes/agents.py @@ -1,8 +1,9 @@ from fastapi import APIRouter, Depends, HTTPException, BackgroundTasks, Response, status from typing import List from sqlalchemy.orm import Session -from app.api.dependencies import ServiceContainer, get_db +from app.api.dependencies import ServiceContainer, get_db, get_current_user from app.api import schemas +from app.db import models from app.db.models.agent import AgentTemplate, AgentInstance, AgentTrigger from app.db.models import Message from app.api.schemas import ( @@ -216,13 +217,14 @@ return {"status": "accepted", "message": "Background task initiated"} @router.post("/{id}/run", status_code=202) - def manual_trigger(id: str, payload: dict, background_tasks: BackgroundTasks, db: Session = Depends(get_db)): + def manual_trigger(id: str, payload: dict, background_tasks: BackgroundTasks, current_user: models.User = Depends(get_current_user), db: Session = Depends(get_db)): instance = db.query(AgentInstance).filter(AgentInstance.id == id).first() if not instance: raise HTTPException(status_code=404, detail="Instance not found") prompt = payload.get("prompt") or f"Manual triggered execution for agent {id}." - background_tasks.add_task(AgentExecutor.run, instance.id, prompt, services, services.user_service) + should_skip = payload.get("skip_coworker", False) + background_tasks.add_task(AgentExecutor.run, instance.id, prompt, services, services.user_service, should_skip) return {"message": "Accepted"} @router.get("/{id}/triggers", response_model=List[schemas.AgentTriggerResponse]) diff --git a/ai-hub/app/core/orchestration/harness_evaluator.py b/ai-hub/app/core/orchestration/harness_evaluator.py index afebd9b..41b28b3 100644 --- a/ai-hub/app/core/orchestration/harness_evaluator.py +++ b/ai-hub/app/core/orchestration/harness_evaluator.py @@ -95,15 +95,32 @@ if not self.assistant: return None start = time.time() - system_prompt = """You are a Quality Control Architect. + # --- File-Based Knowledge Discovery (Aligned with ClaudeCode) --- + coworker_context = "" + try: + cmd_res = self.assistant.dispatch_single( + self.mesh_node_id, + "cat .coworker.md", + session_id=self.sync_workspace_id, + timeout=5 + ) + if cmd_res.get("status") == "SUCCESS": + coworker_context = f"\n\nPROJECT-SPECIFIC CONTEXT (from .coworker.md):\n{cmd_res.get('stdout', '')}" + except: + logger.debug("[HarnessEvaluator] No .coworker.md found; proceeding with generic rubric.") + + system_prompt = f"""You are a Quality Control Architect for a live infrastructure swarm. Your task is to analyze a user request and generate a specific Evaluation Rubric in Markdown. +## Context Discovery (Architectural Constraints): +{coworker_context or "No specific .coworker.md found. Use general best practices for modern infrastructure and swarm orchestration."} + The Rubric MUST include: 1. **Expectations**: A checklist of specific results the agent should satisfy for this specific task. 2. **Core Rubric**: A quantitative scoring guide (0-100) across these dimensions: - **Quality**: Tone, structure, and readability. - **Accuracy**: Completeness and technical correctness. - - **Efficiency (Non-AI Alike)**: Adherence to edicts: No gold-plating, no unnecessary refactors, no redundant docstrings. + - **Efficiency (Non-AI Alike)**: Adherence to edicts. Format as a clean Markdown file with exactly one '# Evaluation Rubric' title.""" @@ -228,6 +245,66 @@ user_id = instance.session.user_id tools = tool_service.get_available_tools(self.db, user_id, feature="agent_harness", session_id=instance.session_id) + # --- Global Blueprint Discovery (Hub Manifesto) --- + global_manifesto = "" + import os + manifesto_path = "/app/blueprints/manifesto.md" + if os.path.exists(manifesto_path): + try: + with open(manifesto_path, "r") as f: + global_manifesto = f"\n\nGLOBAL HUB MANIFESTO (Project Vision):\n{f.read()}" + except: + pass + + # --- Dynamic Knowledge Snapshot (Discovery Step) --- + dynamic_snapshot = "" + try: + # We take a 'Live Snapshot' of the node's status (similar to ClaudeCode's gitStatus) + snap_res = self.assistant.dispatch_single( + self.mesh_node_id, + "uname -a && uptime && df -h /", + session_id=self.sync_workspace_id, + timeout=5 + ) + if snap_res.get("status") == "SUCCESS": + dynamic_snapshot = f"\n\nLIVE SYSTEM SNAPSHOT (Discovery Step):\n{snap_res.get('stdout', '')}" + except: + pass + + # --- File-Based Knowledge Discovery --- + context_chunks = [] + # Inject Global Manifesto if found + if global_manifesto: + context_chunks.append({ + "id": "hub_manifesto", + "content": global_manifesto, + "metadata": {"source": "hub_blueprints", "priority": "critical"} + }) + + # Inject the live snapshot as a transient knowledge chunk + if dynamic_snapshot: + context_chunks.append({ + "id": "runtime_telemetry", + "content": dynamic_snapshot, + "metadata": {"source": "runtime_discovery", "priority": "high"} + }) + + try: + cmd_res = self.assistant.dispatch_single( + self.mesh_node_id, + "cat .coworker.md", + session_id=self.sync_workspace_id, + timeout=5 + ) + if cmd_res.get("status") == "SUCCESS": + context_chunks.append({ + "id": ".coworker.md", + "content": cmd_res.get("stdout", ""), + "metadata": {"source": "local_filesystem", "priority": "high"} + }) + except: + pass + final_answer = "" score = 0 @@ -236,7 +313,7 @@ # We pass no history to ensure "Blind" context async for event in architect.run( question=user_request, - context_chunks=[], + context_chunks=context_chunks, history=[], llm_provider=self.llm_provider, tool_service=tool_service, diff --git a/ai-hub/app/core/orchestration/profiles.py b/ai-hub/app/core/orchestration/profiles.py index 8da0ef5..063a394 100644 --- a/ai-hub/app/core/orchestration/profiles.py +++ b/ai-hub/app/core/orchestration/profiles.py @@ -66,6 +66,25 @@ Answer:""" +EVALUATOR_PROMPT_TEMPLATE = """You are the **Co-Worker Quality Auditor**, a specialized subsystem of the Master-Architect. +Your MISSION is to perform an objective, evidence-based evaluation of another agent's work within our **Live Mesh Swarm**. + +## 🧐 Audit Guidelines: +- **Truth from Execution**: You are operating in a real infrastructure. Rely on your tools (`mesh_terminal_control`, `mesh_file_explorer`) to verify the state of nodes, system stats, and files. +- **Swarm Awareness**: You are aware that we are running on a decentralized mesh (Mac Mini Master, diverse Workers). +- **Knowledge Alignment**: Use the provided RAG Context and Mesh Topology to verify technical details about our specific deployments (e.g., Mac Mini M4 specs, Docker clusters). +- **Rework Directives**: If an agent misses a technical detail provided in the RAG context, provide a 'Directive' to correct it. +- **Score Format**: Provide a numerical score (0-100) and justification. Your final response MUST end with exactly: `FINAL_SCORE: [number]` + +Infrastructure Context (Mesh): +{mesh_context} + +RAG Context (Knowledge Base): +{context} + +Original Request: {question} +""" + # --- Profile Definitions --- class FeatureProfile: @@ -122,7 +141,7 @@ ), "agent_harness": FeatureProfile( name="agent_harness", - template=DEFAULT_PROMPT_TEMPLATE, + template=EVALUATOR_PROMPT_TEMPLATE, autonomous_limit=10 # Snappy evaluation loop ) } diff --git a/ai-hub/app/core/services/rag.py b/ai-hub/app/core/services/rag.py index fcb2e6a..771f2ae 100644 --- a/ai-hub/app/core/services/rag.py +++ b/ai-hub/app/core/services/rag.py @@ -171,7 +171,7 @@ # defensive join: only take enough chunks for ~4000 chars total chunks = [] total_len = 0 - for chunk in reversed(live.terminal_history[-40:]): + for chunk in reversed(list(live.terminal_history)[-40:]): chunks.insert(0, chunk) total_len += len(chunk) if total_len > 4000: break diff --git a/docker-compose.yml b/docker-compose.yml index 1bfaf84..4a83fc4 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -39,6 +39,8 @@ - ./ai-hub/app:/app/app:rw - ./agent-node:/app/agent-node-source:ro - ./skills:/app/skills:ro + - ./docs:/app/docs:ro + - ./blueprints:/app/blueprints:ro - browser_shm:/dev/shm:rw deploy: resources: diff --git a/frontend/src/features/agents/components/AgentDrillDown.js b/frontend/src/features/agents/components/AgentDrillDown.js index 7225e53..82910fe 100644 --- a/frontend/src/features/agents/components/AgentDrillDown.js +++ b/frontend/src/features/agents/components/AgentDrillDown.js @@ -35,7 +35,9 @@ const [cortexFiles, setCortexFiles] = useState([]); const [feedbackContent, setFeedbackContent] = useState(""); const [rubricContent, setRubricContent] = useState(""); + const [coworkerContent, setCoworkerContent] = useState(""); const [historyLog, setHistoryLog] = useState([]); + const [savingGroundTruth, setSavingGroundTruth] = useState(false); // Helper: Convert cron expression to human-readable text const describeCron = (expr) => { @@ -168,6 +170,12 @@ } catch (e) {} } + // Display coworker.md (Ground Truth) + try { + const coworker = await fetchWithAuth(`/agents/${agentId}/cortex/coworker.md?node_id=${found.mesh_node_id}&session_id=${sid}`); + setCoworkerContent(coworker?.content || ""); + } catch (e) {} + // Display history.log if (fileExists("history.log")) { try { @@ -205,9 +213,9 @@ if (!overrideText.trim() || !agent?.session_id) return; try { - await fetchWithAuth(`/agents/${agentId}/webhook?skip_coworker=${skipEval}`, { + await fetchWithAuth(`/agents/${agentId}/run`, { method: "POST", - body: { prompt: overrideText } + body: { prompt: overrideText, skip_coworker: skipEval } }); setOverrideText(""); fetchData(); @@ -314,6 +322,23 @@ } }; + const handleSaveGroundTruth = async () => { + try { + setSavingGroundTruth(true); + const sid = agent.session?.sync_workspace_id || agent.session_id; + await fetchWithAuth(`/agents/${agentId}/cortex/coworker.md?node_id=${agent.mesh_node_id}&session_id=${sid}`, { + method: "PUT", + body: { content: coworkerContent } + }); + setModalConfig({ title: 'Success', message: 'Swarm Ground Truth aligned across all agents.', type: 'success' }); + fetchData(); + } catch (err) { + setModalConfig({ title: 'Update Failed', message: err.message, type: 'error' }); + } finally { + setSavingGroundTruth(false); + } + }; + const handleAddTrigger = async () => { try { setCreatingTrigger(true); @@ -1038,6 +1063,44 @@ {activeTab === 'evaluation' && (
+ {/* Swarm Ground Truth Editor (Aligned Discovery) */} +
+
+ + + Swarm Ground Truth (Aligned Knowledge Base) + +
+ .coworker.md + +
+
+
+ +
+ + This context is injected into both the Main Agent and Co-Worker Auditor. + + + Mirrors ClaudeCode Discovery Style 🛰️ + +
+
+
+
{/* Evaluation Strategy (Rubric) */}