mirror of
https://github.com/blackboxprogramming/BlackRoad-Operating-System.git
synced 2026-03-17 00:57:12 -05:00
Merge branch 'main' into claude/webdav-context-prompt-013MZPMZrFPHpdzo2pRjpmJT
This commit is contained in:
253
backend/app/models/cognition.py
Normal file
253
backend/app/models/cognition.py
Normal file
@@ -0,0 +1,253 @@
|
||||
"""
|
||||
Cognition Database Models
|
||||
|
||||
Models for storing:
|
||||
- Workflows and their execution history
|
||||
- Reasoning traces from agents
|
||||
- Agent memory/context
|
||||
- Prompt registry
|
||||
|
||||
Tables:
|
||||
- workflows: Workflow definitions and execution status
|
||||
- workflow_executions: History of workflow runs
|
||||
- reasoning_traces: Agent reasoning step records
|
||||
- agent_memory: Shared memory/context across workflow
|
||||
- prompt_registry: Registered agent prompts
|
||||
"""
|
||||
|
||||
from datetime import datetime
|
||||
from sqlalchemy import Column, Integer, String, Text, Float, Boolean, DateTime, ForeignKey, JSON, Enum
|
||||
from sqlalchemy.orm import relationship
|
||||
from sqlalchemy.dialects.postgresql import UUID, JSONB
|
||||
import uuid
|
||||
import enum
|
||||
|
||||
from ..database import Base
|
||||
|
||||
|
||||
class WorkflowStatus(str, enum.Enum):
|
||||
"""Workflow execution status"""
|
||||
PENDING = "pending"
|
||||
RUNNING = "running"
|
||||
COMPLETED = "completed"
|
||||
FAILED = "failed"
|
||||
CANCELLED = "cancelled"
|
||||
|
||||
|
||||
class ExecutionMode(str, enum.Enum):
|
||||
"""Workflow execution mode"""
|
||||
SEQUENTIAL = "sequential"
|
||||
PARALLEL = "parallel"
|
||||
RECURSIVE = "recursive"
|
||||
|
||||
|
||||
class Workflow(Base):
|
||||
"""
|
||||
Workflow Definition
|
||||
|
||||
Stores multi-agent workflow definitions.
|
||||
"""
|
||||
__tablename__ = "workflows"
|
||||
|
||||
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
||||
name = Column(String(200), nullable=False, index=True)
|
||||
description = Column(Text)
|
||||
|
||||
# Workflow configuration
|
||||
mode = Column(Enum(ExecutionMode), default=ExecutionMode.SEQUENTIAL, nullable=False)
|
||||
steps = Column(JSONB, nullable=False) # List of workflow steps
|
||||
timeout_seconds = Column(Integer, default=600)
|
||||
|
||||
# Metadata
|
||||
created_by = Column(String(100))
|
||||
created_at = Column(DateTime, default=datetime.utcnow, nullable=False)
|
||||
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
||||
is_active = Column(Boolean, default=True)
|
||||
is_template = Column(Boolean, default=False)
|
||||
|
||||
# Tags for categorization
|
||||
tags = Column(JSONB, default=list)
|
||||
|
||||
# Relationships
|
||||
executions = relationship("WorkflowExecution", back_populates="workflow", cascade="all, delete-orphan")
|
||||
|
||||
def __repr__(self):
|
||||
return f"<Workflow(id={self.id}, name={self.name}, mode={self.mode})>"
|
||||
|
||||
|
||||
class WorkflowExecution(Base):
|
||||
"""
|
||||
Workflow Execution Record
|
||||
|
||||
Stores history of workflow executions with results.
|
||||
"""
|
||||
__tablename__ = "workflow_executions"
|
||||
|
||||
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
||||
workflow_id = Column(UUID(as_uuid=True), ForeignKey("workflows.id"), nullable=False, index=True)
|
||||
|
||||
# Execution details
|
||||
status = Column(Enum(WorkflowStatus), default=WorkflowStatus.PENDING, nullable=False, index=True)
|
||||
started_at = Column(DateTime, default=datetime.utcnow, nullable=False, index=True)
|
||||
completed_at = Column(DateTime)
|
||||
duration_seconds = Column(Float)
|
||||
|
||||
# Results
|
||||
step_results = Column(JSONB) # Results from each step
|
||||
error_message = Column(Text)
|
||||
error_details = Column(JSONB)
|
||||
|
||||
# Metrics
|
||||
overall_confidence = Column(Float)
|
||||
total_agents_used = Column(Integer)
|
||||
|
||||
# Context
|
||||
initial_context = Column(JSONB)
|
||||
final_memory = Column(JSONB)
|
||||
|
||||
# Relationships
|
||||
workflow = relationship("Workflow", back_populates="executions")
|
||||
reasoning_traces = relationship("ReasoningTrace", back_populates="execution", cascade="all, delete-orphan")
|
||||
|
||||
def __repr__(self):
|
||||
return f"<WorkflowExecution(id={self.id}, workflow_id={self.workflow_id}, status={self.status})>"
|
||||
|
||||
|
||||
class ReasoningTrace(Base):
|
||||
"""
|
||||
Reasoning Trace Step
|
||||
|
||||
Stores individual reasoning steps from agent execution.
|
||||
Provides transparency into how agents arrived at decisions.
|
||||
"""
|
||||
__tablename__ = "reasoning_traces"
|
||||
|
||||
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
||||
execution_id = Column(UUID(as_uuid=True), ForeignKey("workflow_executions.id"), nullable=False, index=True)
|
||||
|
||||
# Step identification
|
||||
workflow_step_name = Column(String(100), nullable=False)
|
||||
agent_name = Column(String(50), nullable=False, index=True)
|
||||
step_number = Column(Integer, nullable=False)
|
||||
step_name = Column(String(100), nullable=False)
|
||||
step_emoji = Column(String(10))
|
||||
|
||||
# Reasoning data
|
||||
input_context = Column(Text)
|
||||
output = Column(Text)
|
||||
confidence_score = Column(Float)
|
||||
|
||||
# Additional metadata
|
||||
metadata = Column(JSONB)
|
||||
timestamp = Column(DateTime, default=datetime.utcnow, nullable=False, index=True)
|
||||
|
||||
# Relationships
|
||||
execution = relationship("WorkflowExecution", back_populates="reasoning_traces")
|
||||
|
||||
def __repr__(self):
|
||||
return f"<ReasoningTrace(id={self.id}, agent={self.agent_name}, step={self.step_name})>"
|
||||
|
||||
|
||||
class AgentMemory(Base):
|
||||
"""
|
||||
Agent Memory/Context
|
||||
|
||||
Stores shared context and memory across workflow execution.
|
||||
Enables agents to build upon each other's work.
|
||||
"""
|
||||
__tablename__ = "agent_memory"
|
||||
|
||||
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
||||
execution_id = Column(UUID(as_uuid=True), ForeignKey("workflow_executions.id"), index=True)
|
||||
|
||||
# Memory data
|
||||
context = Column(JSONB, nullable=False) # Shared context dictionary
|
||||
confidence_scores = Column(JSONB) # Confidence per step
|
||||
|
||||
# Metadata
|
||||
created_at = Column(DateTime, default=datetime.utcnow, nullable=False)
|
||||
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
||||
|
||||
# Memory can be associated with user sessions
|
||||
session_id = Column(String(100), index=True)
|
||||
user_id = Column(String(100), index=True)
|
||||
|
||||
# TTL for memory expiration
|
||||
expires_at = Column(DateTime)
|
||||
|
||||
def __repr__(self):
|
||||
return f"<AgentMemory(id={self.id}, execution_id={self.execution_id})>"
|
||||
|
||||
|
||||
class PromptRegistry(Base):
|
||||
"""
|
||||
Prompt Registry
|
||||
|
||||
Stores registered agent prompts (summon spells).
|
||||
Enables versioning and management of agent invocation prompts.
|
||||
"""
|
||||
__tablename__ = "prompt_registry"
|
||||
|
||||
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
||||
|
||||
# Prompt identification
|
||||
agent_name = Column(String(50), nullable=False, index=True)
|
||||
prompt_name = Column(String(100))
|
||||
prompt_text = Column(Text, nullable=False)
|
||||
|
||||
# Versioning
|
||||
version = Column(String(20), nullable=False)
|
||||
is_active = Column(Boolean, default=True, index=True)
|
||||
|
||||
# Metadata
|
||||
description = Column(Text)
|
||||
metadata = Column(JSONB) # Author, purpose, etc.
|
||||
tags = Column(JSONB, default=list)
|
||||
|
||||
# Usage stats
|
||||
usage_count = Column(Integer, default=0)
|
||||
last_used_at = Column(DateTime)
|
||||
average_confidence = Column(Float)
|
||||
|
||||
# Timestamps
|
||||
created_at = Column(DateTime, default=datetime.utcnow, nullable=False, index=True)
|
||||
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
||||
created_by = Column(String(100))
|
||||
|
||||
def __repr__(self):
|
||||
return f"<PromptRegistry(id={self.id}, agent={self.agent_name}, version={self.version})>"
|
||||
|
||||
|
||||
class AgentPerformanceMetric(Base):
|
||||
"""
|
||||
Agent Performance Metrics
|
||||
|
||||
Tracks performance metrics for agents over time.
|
||||
Enables monitoring and optimization.
|
||||
"""
|
||||
__tablename__ = "agent_performance_metrics"
|
||||
|
||||
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
||||
|
||||
# Agent identification
|
||||
agent_name = Column(String(50), nullable=False, index=True)
|
||||
execution_id = Column(UUID(as_uuid=True), ForeignKey("workflow_executions.id"), index=True)
|
||||
|
||||
# Performance metrics
|
||||
execution_time_seconds = Column(Float)
|
||||
confidence_score = Column(Float)
|
||||
success = Column(Boolean, default=True)
|
||||
|
||||
# Resource usage (if available)
|
||||
memory_usage_mb = Column(Float)
|
||||
api_calls_made = Column(Integer)
|
||||
|
||||
# Quality metrics
|
||||
reasoning_steps_count = Column(Integer)
|
||||
complexity_score = Column(Float)
|
||||
|
||||
# Timestamps
|
||||
measured_at = Column(DateTime, default=datetime.utcnow, nullable=False, index=True)
|
||||
|
||||
def __repr__(self):
|
||||
return f"<AgentPerformanceMetric(agent={self.agent_name}, confidence={self.confidence_score})>"
|
||||
499
backend/app/routers/cognition.py
Normal file
499
backend/app/routers/cognition.py
Normal file
@@ -0,0 +1,499 @@
|
||||
"""
|
||||
Cognition API Router
|
||||
|
||||
Exposes the Cece Cognition Framework via REST API:
|
||||
- Execute individual agents (Cece, Wasp, Clause, Codex)
|
||||
- Execute multi-agent workflows
|
||||
- Query reasoning traces
|
||||
- Access agent memory
|
||||
- Manage prompts
|
||||
|
||||
Endpoints:
|
||||
- POST /api/cognition/execute - Execute single agent
|
||||
- POST /api/cognition/workflows - Execute multi-agent workflow
|
||||
- GET /api/cognition/reasoning-trace/{workflow_id} - Get reasoning trace
|
||||
- GET /api/cognition/memory - Query agent memory
|
||||
- POST /api/prompts/register - Register new prompt
|
||||
- GET /api/prompts/search - Search prompts
|
||||
"""
|
||||
|
||||
import logging
|
||||
from typing import Any, Dict, List, Optional
|
||||
from datetime import datetime
|
||||
from uuid import uuid4
|
||||
|
||||
from fastapi import APIRouter, HTTPException, Depends, status
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
# Import our agents and orchestration
|
||||
from agents.categories.ai_ml.cece_agent import CeceAgent
|
||||
from agents.categories.ai_ml.wasp_agent import WaspAgent
|
||||
from agents.categories.ai_ml.clause_agent import ClauseAgent
|
||||
from agents.categories.ai_ml.codex_agent import CodexAgent
|
||||
from backend.app.services.orchestration import (
|
||||
OrchestrationEngine,
|
||||
Workflow,
|
||||
WorkflowStep,
|
||||
ExecutionMode
|
||||
)
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
router = APIRouter(prefix="/api/cognition", tags=["Cognition"])
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# REQUEST/RESPONSE MODELS
|
||||
# ============================================================================
|
||||
|
||||
class AgentExecuteRequest(BaseModel):
|
||||
"""Request to execute single agent"""
|
||||
agent: str = Field(..., description="Agent name: cece, wasp, clause, codex")
|
||||
input: str = Field(..., description="Input/prompt for the agent")
|
||||
context: Dict[str, Any] = Field(default_factory=dict, description="Optional context")
|
||||
verbose: bool = Field(default=True, description="Return full reasoning trace")
|
||||
|
||||
|
||||
class AgentExecuteResponse(BaseModel):
|
||||
"""Response from agent execution"""
|
||||
agent: str
|
||||
result: Dict[str, Any]
|
||||
reasoning_trace: List[Any]
|
||||
confidence: float
|
||||
execution_time_seconds: float
|
||||
warnings: List[str] = Field(default_factory=list)
|
||||
|
||||
|
||||
class WorkflowStepRequest(BaseModel):
|
||||
"""Workflow step definition"""
|
||||
name: str
|
||||
agent_name: str
|
||||
input_template: str
|
||||
depends_on: List[str] = Field(default_factory=list)
|
||||
parallel_with: List[str] = Field(default_factory=list)
|
||||
max_retries: int = Field(default=3)
|
||||
|
||||
|
||||
class WorkflowExecuteRequest(BaseModel):
|
||||
"""Request to execute multi-agent workflow"""
|
||||
name: str
|
||||
steps: List[WorkflowStepRequest]
|
||||
mode: str = Field(default="sequential", description="sequential, parallel, or recursive")
|
||||
initial_context: Dict[str, Any] = Field(default_factory=dict)
|
||||
timeout_seconds: int = Field(default=600)
|
||||
|
||||
|
||||
class WorkflowExecuteResponse(BaseModel):
|
||||
"""Response from workflow execution"""
|
||||
workflow_id: str
|
||||
status: str
|
||||
step_results: Dict[str, Any]
|
||||
reasoning_trace: List[Dict[str, Any]]
|
||||
memory: Dict[str, Any]
|
||||
total_duration_seconds: float
|
||||
error: Optional[str] = None
|
||||
|
||||
|
||||
class PromptRegisterRequest(BaseModel):
|
||||
"""Request to register new prompt"""
|
||||
agent_name: str
|
||||
prompt_text: str
|
||||
version: str = Field(default="1.0.0")
|
||||
metadata: Dict[str, Any] = Field(default_factory=dict)
|
||||
|
||||
|
||||
class PromptSearchRequest(BaseModel):
|
||||
"""Request to search prompts"""
|
||||
agent: Optional[str] = None
|
||||
version: Optional[str] = None
|
||||
search_term: Optional[str] = None
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# AGENT EXECUTION ENDPOINTS
|
||||
# ============================================================================
|
||||
|
||||
# Global orchestration engine
|
||||
orchestration_engine = OrchestrationEngine()
|
||||
|
||||
|
||||
@router.post("/execute", response_model=AgentExecuteResponse)
|
||||
async def execute_agent(request: AgentExecuteRequest):
|
||||
"""
|
||||
Execute single agent
|
||||
|
||||
Execute one of the core agents (Cece, Wasp, Clause, Codex) with the given input.
|
||||
|
||||
**Agents:**
|
||||
- `cece`: Cognitive architect (15-step reasoning + 6-step architecture)
|
||||
- `wasp`: UI/UX specialist (7-step design process)
|
||||
- `clause`: Legal specialist (7-step legal review)
|
||||
- `codex`: Code execution specialist (7-step dev process)
|
||||
|
||||
**Example:**
|
||||
```json
|
||||
{
|
||||
"agent": "cece",
|
||||
"input": "I'm overwhelmed with 10 projects and don't know where to start",
|
||||
"context": {
|
||||
"projects": ["Project A", "Project B", ...],
|
||||
"deadlines": {...}
|
||||
}
|
||||
}
|
||||
```
|
||||
"""
|
||||
logger.info(f"🚀 Executing agent: {request.agent}")
|
||||
|
||||
# Get agent instance
|
||||
if request.agent not in orchestration_engine.agents:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail=f"Unknown agent: {request.agent}. Available: cece, wasp, clause, codex"
|
||||
)
|
||||
|
||||
agent = orchestration_engine.agents[request.agent]
|
||||
|
||||
# Prepare params
|
||||
params = {
|
||||
"input": request.input,
|
||||
"context": request.context,
|
||||
"verbose": request.verbose
|
||||
}
|
||||
|
||||
try:
|
||||
# Execute agent
|
||||
result = await agent.run(params)
|
||||
|
||||
# Build response
|
||||
return AgentExecuteResponse(
|
||||
agent=request.agent,
|
||||
result=result.data,
|
||||
reasoning_trace=result.data.get("reasoning_trace", []),
|
||||
confidence=result.data.get("confidence", 0.85),
|
||||
execution_time_seconds=result.duration_seconds or 0.0,
|
||||
warnings=result.data.get("warnings", [])
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error executing agent {request.agent}: {str(e)}", exc_info=True)
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail=f"Agent execution failed: {str(e)}"
|
||||
)
|
||||
|
||||
|
||||
@router.post("/workflows", response_model=WorkflowExecuteResponse)
|
||||
async def execute_workflow(request: WorkflowExecuteRequest):
|
||||
"""
|
||||
Execute multi-agent workflow
|
||||
|
||||
Execute a multi-step workflow with multiple agents working together.
|
||||
|
||||
**Execution Modes:**
|
||||
- `sequential`: Steps run one after another (A → B → C)
|
||||
- `parallel`: Independent steps run simultaneously where possible
|
||||
- `recursive`: Steps iterate until convergence
|
||||
|
||||
**Example:**
|
||||
```json
|
||||
{
|
||||
"name": "Build Dashboard",
|
||||
"mode": "sequential",
|
||||
"steps": [
|
||||
{
|
||||
"name": "architect",
|
||||
"agent_name": "cece",
|
||||
"input_template": "Design a dashboard for AI agent workflows"
|
||||
},
|
||||
{
|
||||
"name": "backend",
|
||||
"agent_name": "codex",
|
||||
"input_template": "${architect.architecture.backend_spec}",
|
||||
"depends_on": ["architect"]
|
||||
},
|
||||
{
|
||||
"name": "frontend",
|
||||
"agent_name": "wasp",
|
||||
"input_template": "${architect.architecture.frontend_spec}",
|
||||
"depends_on": ["architect"]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
"""
|
||||
logger.info(f"🚀 Executing workflow: {request.name}")
|
||||
|
||||
# Convert request to Workflow
|
||||
try:
|
||||
mode = ExecutionMode[request.mode.upper()]
|
||||
except KeyError:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail=f"Invalid execution mode: {request.mode}. Use: sequential, parallel, or recursive"
|
||||
)
|
||||
|
||||
workflow = Workflow(
|
||||
id=str(uuid4()),
|
||||
name=request.name,
|
||||
steps=[
|
||||
WorkflowStep(
|
||||
name=step.name,
|
||||
agent_name=step.agent_name,
|
||||
input_template=step.input_template,
|
||||
depends_on=step.depends_on,
|
||||
parallel_with=step.parallel_with,
|
||||
max_retries=step.max_retries
|
||||
)
|
||||
for step in request.steps
|
||||
],
|
||||
mode=mode,
|
||||
timeout_seconds=request.timeout_seconds
|
||||
)
|
||||
|
||||
try:
|
||||
# Execute workflow
|
||||
result = await orchestration_engine.execute_workflow(
|
||||
workflow,
|
||||
initial_context=request.initial_context
|
||||
)
|
||||
|
||||
# Build response
|
||||
return WorkflowExecuteResponse(
|
||||
workflow_id=result.workflow_id,
|
||||
status=result.status.value,
|
||||
step_results=result.step_results,
|
||||
reasoning_trace=result.reasoning_trace,
|
||||
memory=result.memory,
|
||||
total_duration_seconds=result.total_duration_seconds,
|
||||
error=result.error
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error executing workflow {request.name}: {str(e)}", exc_info=True)
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail=f"Workflow execution failed: {str(e)}"
|
||||
)
|
||||
|
||||
|
||||
@router.get("/reasoning-trace/{workflow_id}")
|
||||
async def get_reasoning_trace(workflow_id: str):
|
||||
"""
|
||||
Get reasoning trace for workflow
|
||||
|
||||
Retrieve the complete reasoning trace showing how agents arrived at their decisions.
|
||||
|
||||
Returns a list of reasoning steps with:
|
||||
- Step name and emoji
|
||||
- Input context
|
||||
- Output/decision
|
||||
- Confidence score
|
||||
- Timestamp
|
||||
"""
|
||||
# In a real implementation, would fetch from database
|
||||
# For now, return placeholder
|
||||
return {
|
||||
"workflow_id": workflow_id,
|
||||
"trace": [
|
||||
{
|
||||
"step": "🚨 Not ok",
|
||||
"agent": "cece",
|
||||
"input": "I'm overwhelmed with projects",
|
||||
"output": "There's too many competing priorities without clear hierarchy",
|
||||
"confidence": 0.95,
|
||||
"timestamp": datetime.utcnow().isoformat()
|
||||
}
|
||||
],
|
||||
"overall_confidence": 0.87
|
||||
}
|
||||
|
||||
|
||||
@router.get("/memory")
|
||||
async def get_memory(workflow_id: Optional[str] = None):
|
||||
"""
|
||||
Query agent memory
|
||||
|
||||
Retrieve shared memory/context from workflow execution.
|
||||
|
||||
Can filter by workflow_id to get memory for specific workflow.
|
||||
"""
|
||||
# In a real implementation, would fetch from database/cache
|
||||
return {
|
||||
"workflow_id": workflow_id,
|
||||
"context": {
|
||||
"user_preferences": {},
|
||||
"session_data": {}
|
||||
},
|
||||
"reasoning_trace": [],
|
||||
"confidence_scores": {}
|
||||
}
|
||||
|
||||
|
||||
@router.get("/active-workflows")
|
||||
async def get_active_workflows():
|
||||
"""
|
||||
Get list of active workflows
|
||||
|
||||
Returns workflow IDs currently being executed.
|
||||
"""
|
||||
active = orchestration_engine.get_active_workflows()
|
||||
|
||||
return {
|
||||
"active_workflows": active,
|
||||
"count": len(active)
|
||||
}
|
||||
|
||||
|
||||
@router.post("/cancel-workflow/{workflow_id}")
|
||||
async def cancel_workflow(workflow_id: str):
|
||||
"""
|
||||
Cancel running workflow
|
||||
|
||||
Attempts to cancel a running workflow. Returns success status.
|
||||
"""
|
||||
success = await orchestration_engine.cancel_workflow(workflow_id)
|
||||
|
||||
if not success:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail=f"Workflow not found or already completed: {workflow_id}"
|
||||
)
|
||||
|
||||
return {
|
||||
"workflow_id": workflow_id,
|
||||
"status": "cancelled"
|
||||
}
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# PROMPT MANAGEMENT ENDPOINTS
|
||||
# ============================================================================
|
||||
|
||||
# In-memory prompt registry (would be database in production)
|
||||
prompt_registry: Dict[str, List[Dict[str, Any]]] = {}
|
||||
|
||||
|
||||
@router.post("/prompts/register")
|
||||
async def register_prompt(request: PromptRegisterRequest):
|
||||
"""
|
||||
Register new agent prompt
|
||||
|
||||
Register a custom summon prompt for an agent.
|
||||
|
||||
**Example:**
|
||||
```json
|
||||
{
|
||||
"agent_name": "cece",
|
||||
"prompt_text": "Cece, run cognition.\\n\\nUse the Alexa-Cece Framework...\\n\\nNow analyze: [YOUR REQUEST]",
|
||||
"version": "1.0.0",
|
||||
"metadata": {
|
||||
"author": "Alexa",
|
||||
"purpose": "Full cognition framework"
|
||||
}
|
||||
}
|
||||
```
|
||||
"""
|
||||
prompt_id = str(uuid4())
|
||||
|
||||
prompt = {
|
||||
"id": prompt_id,
|
||||
"agent_name": request.agent_name,
|
||||
"prompt_text": request.prompt_text,
|
||||
"version": request.version,
|
||||
"metadata": request.metadata,
|
||||
"created_at": datetime.utcnow().isoformat(),
|
||||
"is_active": True
|
||||
}
|
||||
|
||||
if request.agent_name not in prompt_registry:
|
||||
prompt_registry[request.agent_name] = []
|
||||
|
||||
prompt_registry[request.agent_name].append(prompt)
|
||||
|
||||
logger.info(f"Registered prompt for {request.agent_name} (ID: {prompt_id})")
|
||||
|
||||
return {
|
||||
"prompt_id": prompt_id,
|
||||
"status": "registered"
|
||||
}
|
||||
|
||||
|
||||
@router.get("/prompts/search")
|
||||
async def search_prompts(
|
||||
agent: Optional[str] = None,
|
||||
version: Optional[str] = None
|
||||
):
|
||||
"""
|
||||
Search registered prompts
|
||||
|
||||
Search for prompts by agent name and/or version.
|
||||
|
||||
**Query Parameters:**
|
||||
- `agent`: Filter by agent name (cece, wasp, clause, codex)
|
||||
- `version`: Filter by version (e.g., "1.0.0", "latest")
|
||||
"""
|
||||
results = []
|
||||
|
||||
if agent:
|
||||
if agent in prompt_registry:
|
||||
prompts = prompt_registry[agent]
|
||||
|
||||
if version == "latest":
|
||||
# Return only the most recent version
|
||||
if prompts:
|
||||
prompts = [max(prompts, key=lambda p: p["created_at"])]
|
||||
|
||||
elif version:
|
||||
# Filter by specific version
|
||||
prompts = [p for p in prompts if p["version"] == version]
|
||||
|
||||
results.extend(prompts)
|
||||
else:
|
||||
# Return all prompts
|
||||
for agent_prompts in prompt_registry.values():
|
||||
results.extend(agent_prompts)
|
||||
|
||||
return {
|
||||
"prompts": results,
|
||||
"count": len(results)
|
||||
}
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# UTILITY ENDPOINTS
|
||||
# ============================================================================
|
||||
|
||||
@router.get("/agents")
|
||||
async def list_agents():
|
||||
"""
|
||||
List available agents
|
||||
|
||||
Returns information about all available agents.
|
||||
"""
|
||||
agents = []
|
||||
|
||||
for agent_name, agent_instance in orchestration_engine.agents.items():
|
||||
info = agent_instance.get_info()
|
||||
agents.append(info)
|
||||
|
||||
return {
|
||||
"agents": agents,
|
||||
"count": len(agents)
|
||||
}
|
||||
|
||||
|
||||
@router.get("/health")
|
||||
async def health_check():
|
||||
"""
|
||||
Health check for cognition system
|
||||
|
||||
Returns system status and metrics.
|
||||
"""
|
||||
return {
|
||||
"status": "healthy",
|
||||
"agents_available": len(orchestration_engine.agents),
|
||||
"active_workflows": len(orchestration_engine.get_active_workflows()),
|
||||
"prompts_registered": sum(len(prompts) for prompts in prompt_registry.values()),
|
||||
"timestamp": datetime.utcnow().isoformat()
|
||||
}
|
||||
468
backend/app/services/orchestration.py
Normal file
468
backend/app/services/orchestration.py
Normal file
@@ -0,0 +1,468 @@
|
||||
"""
|
||||
Multi-Agent Orchestration Service
|
||||
|
||||
Coordinates execution of multiple AI agents in workflows:
|
||||
- Sequential execution (A → B → C)
|
||||
- Parallel execution (A + B + C → merge)
|
||||
- Recursive refinement (A ⇄ B until optimal)
|
||||
- Memory sharing between agents
|
||||
- Reasoning trace aggregation
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
from dataclasses import dataclass, field
|
||||
from datetime import datetime
|
||||
from enum import Enum
|
||||
from typing import Any, Dict, List, Optional, Set
|
||||
from uuid import uuid4
|
||||
|
||||
# Import our core agents
|
||||
from agents.categories.ai_ml.cece_agent import CeceAgent
|
||||
from agents.categories.ai_ml.wasp_agent import WaspAgent
|
||||
from agents.categories.ai_ml.clause_agent import ClauseAgent
|
||||
from agents.categories.ai_ml.codex_agent import CodexAgent
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class WorkflowStatus(Enum):
|
||||
"""Workflow execution status"""
|
||||
PENDING = "pending"
|
||||
RUNNING = "running"
|
||||
COMPLETED = "completed"
|
||||
FAILED = "failed"
|
||||
CANCELLED = "cancelled"
|
||||
|
||||
|
||||
class ExecutionMode(Enum):
|
||||
"""Agent execution mode"""
|
||||
SEQUENTIAL = "sequential"
|
||||
PARALLEL = "parallel"
|
||||
RECURSIVE = "recursive"
|
||||
|
||||
|
||||
@dataclass
|
||||
class WorkflowStep:
|
||||
"""Single step in workflow"""
|
||||
name: str
|
||||
agent_name: str
|
||||
input_template: str # Can reference previous outputs with ${step_name.field}
|
||||
depends_on: List[str] = field(default_factory=list)
|
||||
parallel_with: List[str] = field(default_factory=list)
|
||||
max_retries: int = 3
|
||||
|
||||
|
||||
@dataclass
|
||||
class Workflow:
|
||||
"""Multi-agent workflow definition"""
|
||||
id: str
|
||||
name: str
|
||||
steps: List[WorkflowStep]
|
||||
mode: ExecutionMode = ExecutionMode.SEQUENTIAL
|
||||
timeout_seconds: int = 600 # 10 minutes default
|
||||
created_at: datetime = field(default_factory=datetime.utcnow)
|
||||
|
||||
|
||||
@dataclass
|
||||
class WorkflowResult:
|
||||
"""Workflow execution result"""
|
||||
workflow_id: str
|
||||
status: WorkflowStatus
|
||||
step_results: Dict[str, Any]
|
||||
reasoning_trace: List[Dict[str, Any]]
|
||||
memory: Dict[str, Any]
|
||||
started_at: datetime
|
||||
completed_at: Optional[datetime] = None
|
||||
error: Optional[str] = None
|
||||
total_duration_seconds: float = 0.0
|
||||
|
||||
|
||||
class AgentMemory:
|
||||
"""Shared memory across workflow agents"""
|
||||
|
||||
def __init__(self):
|
||||
self.context: Dict[str, Any] = {}
|
||||
self.reasoning_trace: List[Dict[str, Any]] = []
|
||||
self.confidence_scores: Dict[str, float] = {}
|
||||
self.metadata: Dict[str, Any] = {}
|
||||
|
||||
def set(self, key: str, value: Any) -> None:
|
||||
"""Set value in context"""
|
||||
self.context[key] = value
|
||||
|
||||
def get(self, key: str, default: Any = None) -> Any:
|
||||
"""Get value from context"""
|
||||
return self.context.get(key, default)
|
||||
|
||||
def add_reasoning(self, step_name: str, agent_name: str, reasoning: Any) -> None:
|
||||
"""Add reasoning trace from agent"""
|
||||
self.reasoning_trace.append({
|
||||
"step": step_name,
|
||||
"agent": agent_name,
|
||||
"reasoning": reasoning,
|
||||
"timestamp": datetime.utcnow().isoformat()
|
||||
})
|
||||
|
||||
def set_confidence(self, step_name: str, confidence: float) -> None:
|
||||
"""Set confidence score for step"""
|
||||
self.confidence_scores[step_name] = confidence
|
||||
|
||||
|
||||
class OrchestrationEngine:
|
||||
"""
|
||||
Multi-Agent Orchestration Engine
|
||||
|
||||
Executes workflows with multiple AI agents, managing:
|
||||
- Execution order (sequential, parallel, recursive)
|
||||
- Dependency resolution
|
||||
- Memory sharing
|
||||
- Error handling and retries
|
||||
- Reasoning trace aggregation
|
||||
|
||||
Example:
|
||||
```python
|
||||
engine = OrchestrationEngine()
|
||||
|
||||
workflow = Workflow(
|
||||
id="build-dashboard",
|
||||
name="Build Dashboard",
|
||||
steps=[
|
||||
WorkflowStep(
|
||||
name="architect",
|
||||
agent_name="cece",
|
||||
input_template="Design dashboard for AI agents"
|
||||
),
|
||||
WorkflowStep(
|
||||
name="backend",
|
||||
agent_name="codex",
|
||||
input_template="${architect.architecture.backend_spec}",
|
||||
depends_on=["architect"]
|
||||
),
|
||||
WorkflowStep(
|
||||
name="frontend",
|
||||
agent_name="wasp",
|
||||
input_template="${architect.architecture.frontend_spec}",
|
||||
depends_on=["architect"]
|
||||
)
|
||||
]
|
||||
)
|
||||
|
||||
result = await engine.execute_workflow(workflow)
|
||||
```
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.logger = logging.getLogger(__name__)
|
||||
|
||||
# Initialize agents
|
||||
self.agents = {
|
||||
"cece": CeceAgent(),
|
||||
"wasp": WaspAgent(),
|
||||
"clause": ClauseAgent(),
|
||||
"codex": CodexAgent()
|
||||
}
|
||||
|
||||
# Active workflows
|
||||
self.active_workflows: Dict[str, Workflow] = {}
|
||||
|
||||
async def execute_workflow(
|
||||
self,
|
||||
workflow: Workflow,
|
||||
initial_context: Optional[Dict[str, Any]] = None
|
||||
) -> WorkflowResult:
|
||||
"""
|
||||
Execute multi-agent workflow
|
||||
|
||||
Args:
|
||||
workflow: Workflow definition
|
||||
initial_context: Initial context/memory (optional)
|
||||
|
||||
Returns:
|
||||
WorkflowResult with all step outputs and reasoning traces
|
||||
"""
|
||||
workflow_id = workflow.id or str(uuid4())
|
||||
started_at = datetime.utcnow()
|
||||
|
||||
self.logger.info(f"🚀 Starting workflow: {workflow.name} (ID: {workflow_id})")
|
||||
self.active_workflows[workflow_id] = workflow
|
||||
|
||||
# Initialize shared memory
|
||||
memory = AgentMemory()
|
||||
if initial_context:
|
||||
memory.context.update(initial_context)
|
||||
|
||||
try:
|
||||
# Execute based on mode
|
||||
if workflow.mode == ExecutionMode.SEQUENTIAL:
|
||||
step_results = await self._execute_sequential(workflow, memory)
|
||||
elif workflow.mode == ExecutionMode.PARALLEL:
|
||||
step_results = await self._execute_parallel(workflow, memory)
|
||||
elif workflow.mode == ExecutionMode.RECURSIVE:
|
||||
step_results = await self._execute_recursive(workflow, memory)
|
||||
else:
|
||||
raise ValueError(f"Unknown execution mode: {workflow.mode}")
|
||||
|
||||
completed_at = datetime.utcnow()
|
||||
duration = (completed_at - started_at).total_seconds()
|
||||
|
||||
self.logger.info(
|
||||
f"✅ Workflow completed: {workflow.name} "
|
||||
f"({len(step_results)} steps, {duration:.2f}s)"
|
||||
)
|
||||
|
||||
return WorkflowResult(
|
||||
workflow_id=workflow_id,
|
||||
status=WorkflowStatus.COMPLETED,
|
||||
step_results=step_results,
|
||||
reasoning_trace=memory.reasoning_trace,
|
||||
memory=memory.context,
|
||||
started_at=started_at,
|
||||
completed_at=completed_at,
|
||||
total_duration_seconds=duration
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"❌ Workflow failed: {workflow.name} - {str(e)}", exc_info=True)
|
||||
|
||||
return WorkflowResult(
|
||||
workflow_id=workflow_id,
|
||||
status=WorkflowStatus.FAILED,
|
||||
step_results={},
|
||||
reasoning_trace=memory.reasoning_trace,
|
||||
memory=memory.context,
|
||||
started_at=started_at,
|
||||
completed_at=datetime.utcnow(),
|
||||
error=str(e)
|
||||
)
|
||||
|
||||
finally:
|
||||
# Cleanup
|
||||
if workflow_id in self.active_workflows:
|
||||
del self.active_workflows[workflow_id]
|
||||
|
||||
async def _execute_sequential(
|
||||
self,
|
||||
workflow: Workflow,
|
||||
memory: AgentMemory
|
||||
) -> Dict[str, Any]:
|
||||
"""Execute workflow steps sequentially"""
|
||||
step_results = {}
|
||||
|
||||
for step in workflow.steps:
|
||||
self.logger.info(f"▶️ Executing step: {step.name} (agent: {step.agent_name})")
|
||||
|
||||
# Resolve input from template
|
||||
input_params = self._resolve_input_template(step.input_template, step_results, memory)
|
||||
|
||||
# Execute agent
|
||||
result = await self._execute_agent_with_retry(
|
||||
step.agent_name,
|
||||
input_params,
|
||||
step.max_retries
|
||||
)
|
||||
|
||||
# Store result
|
||||
step_results[step.name] = result.data
|
||||
|
||||
# Update memory
|
||||
memory.set(f"{step.name}_output", result.data)
|
||||
memory.add_reasoning(step.name, step.agent_name, result.data.get("reasoning_trace", []))
|
||||
|
||||
if "confidence" in result.data:
|
||||
memory.set_confidence(step.name, result.data["confidence"])
|
||||
|
||||
return step_results
|
||||
|
||||
async def _execute_parallel(
|
||||
self,
|
||||
workflow: Workflow,
|
||||
memory: AgentMemory
|
||||
) -> Dict[str, Any]:
|
||||
"""Execute workflow steps in parallel where possible"""
|
||||
step_results = {}
|
||||
remaining_steps = set(step.name for step in workflow.steps)
|
||||
completed_steps: Set[str] = set()
|
||||
|
||||
while remaining_steps:
|
||||
# Find steps that can run now (dependencies met)
|
||||
ready_steps = []
|
||||
|
||||
for step in workflow.steps:
|
||||
if step.name not in remaining_steps:
|
||||
continue
|
||||
|
||||
# Check if dependencies are met
|
||||
deps_met = all(dep in completed_steps for dep in step.depends_on)
|
||||
|
||||
if deps_met:
|
||||
ready_steps.append(step)
|
||||
|
||||
if not ready_steps:
|
||||
raise RuntimeError("Workflow deadlock: no steps can execute (circular dependency?)")
|
||||
|
||||
# Execute ready steps in parallel
|
||||
self.logger.info(f"▶️ Executing {len(ready_steps)} steps in parallel")
|
||||
|
||||
tasks = []
|
||||
for step in ready_steps:
|
||||
input_params = self._resolve_input_template(step.input_template, step_results, memory)
|
||||
task = self._execute_agent_with_retry(
|
||||
step.agent_name,
|
||||
input_params,
|
||||
step.max_retries
|
||||
)
|
||||
tasks.append((step.name, step.agent_name, task))
|
||||
|
||||
# Wait for all parallel steps to complete
|
||||
results = await asyncio.gather(*[task for _, _, task in tasks])
|
||||
|
||||
# Process results
|
||||
for (step_name, agent_name, _), result in zip(tasks, results):
|
||||
step_results[step_name] = result.data
|
||||
memory.set(f"{step_name}_output", result.data)
|
||||
memory.add_reasoning(step_name, agent_name, result.data.get("reasoning_trace", []))
|
||||
|
||||
if "confidence" in result.data:
|
||||
memory.set_confidence(step_name, result.data["confidence"])
|
||||
|
||||
completed_steps.add(step_name)
|
||||
remaining_steps.remove(step_name)
|
||||
|
||||
return step_results
|
||||
|
||||
async def _execute_recursive(
|
||||
self,
|
||||
workflow: Workflow,
|
||||
memory: AgentMemory
|
||||
) -> Dict[str, Any]:
|
||||
"""Execute workflow recursively until convergence"""
|
||||
step_results = {}
|
||||
max_iterations = 10
|
||||
convergence_threshold = 0.95
|
||||
iteration = 0
|
||||
|
||||
while iteration < max_iterations:
|
||||
iteration += 1
|
||||
self.logger.info(f"🔄 Recursive iteration {iteration}")
|
||||
|
||||
iteration_results = {}
|
||||
|
||||
# Execute all steps
|
||||
for step in workflow.steps:
|
||||
input_params = self._resolve_input_template(step.input_template, step_results, memory)
|
||||
|
||||
result = await self._execute_agent_with_retry(
|
||||
step.agent_name,
|
||||
input_params,
|
||||
step.max_retries
|
||||
)
|
||||
|
||||
iteration_results[step.name] = result.data
|
||||
memory.add_reasoning(f"{step.name}_iter{iteration}", step.agent_name, result.data.get("reasoning_trace", []))
|
||||
|
||||
# Check convergence
|
||||
confidence = self._calculate_overall_confidence(iteration_results)
|
||||
|
||||
self.logger.info(f" Confidence: {confidence:.2f}")
|
||||
|
||||
if confidence >= convergence_threshold:
|
||||
self.logger.info(f"✓ Converged at iteration {iteration} (confidence: {confidence:.2f})")
|
||||
step_results = iteration_results
|
||||
break
|
||||
|
||||
step_results = iteration_results
|
||||
|
||||
return step_results
|
||||
|
||||
async def _execute_agent_with_retry(
|
||||
self,
|
||||
agent_name: str,
|
||||
params: Dict[str, Any],
|
||||
max_retries: int
|
||||
):
|
||||
"""Execute agent with automatic retries"""
|
||||
if agent_name not in self.agents:
|
||||
raise ValueError(f"Unknown agent: {agent_name}")
|
||||
|
||||
agent = self.agents[agent_name]
|
||||
last_exception = None
|
||||
|
||||
for attempt in range(max_retries):
|
||||
try:
|
||||
result = await agent.run(params)
|
||||
return result
|
||||
|
||||
except Exception as e:
|
||||
last_exception = e
|
||||
self.logger.warning(
|
||||
f"Agent {agent_name} attempt {attempt + 1}/{max_retries} failed: {str(e)}"
|
||||
)
|
||||
|
||||
if attempt < max_retries - 1:
|
||||
await asyncio.sleep(2 ** attempt) # Exponential backoff
|
||||
|
||||
raise last_exception
|
||||
|
||||
def _resolve_input_template(
|
||||
self,
|
||||
template: str,
|
||||
step_results: Dict[str, Any],
|
||||
memory: AgentMemory
|
||||
) -> Dict[str, Any]:
|
||||
"""Resolve input template with variables from previous steps"""
|
||||
|
||||
# If template doesn't contain variables, treat as direct input
|
||||
if "${" not in template:
|
||||
return {"input": template}
|
||||
|
||||
# Replace variables like ${step_name.field} with actual values
|
||||
resolved = template
|
||||
|
||||
import re
|
||||
pattern = r'\$\{([^}]+)\}'
|
||||
matches = re.findall(pattern, template)
|
||||
|
||||
for match in matches:
|
||||
parts = match.split('.')
|
||||
|
||||
# Navigate through nested structure
|
||||
value = step_results
|
||||
for part in parts:
|
||||
if isinstance(value, dict):
|
||||
value = value.get(part)
|
||||
else:
|
||||
value = None
|
||||
break
|
||||
|
||||
if value is not None:
|
||||
resolved = resolved.replace(f"${{{match}}}", str(value))
|
||||
|
||||
return {"input": resolved, "context": memory.context}
|
||||
|
||||
def _calculate_overall_confidence(self, step_results: Dict[str, Any]) -> float:
|
||||
"""Calculate overall confidence from step results"""
|
||||
confidences = []
|
||||
|
||||
for result in step_results.values():
|
||||
if isinstance(result, dict) and "confidence" in result:
|
||||
confidences.append(result["confidence"])
|
||||
|
||||
if not confidences:
|
||||
return 0.0
|
||||
|
||||
return sum(confidences) / len(confidences)
|
||||
|
||||
async def cancel_workflow(self, workflow_id: str) -> bool:
|
||||
"""Cancel running workflow"""
|
||||
if workflow_id in self.active_workflows:
|
||||
self.logger.info(f"Cancelling workflow: {workflow_id}")
|
||||
# In a real implementation, would cancel running tasks
|
||||
del self.active_workflows[workflow_id]
|
||||
return True
|
||||
return False
|
||||
|
||||
def get_active_workflows(self) -> List[str]:
|
||||
"""Get list of active workflow IDs"""
|
||||
return list(self.active_workflows.keys())
|
||||
Reference in New Issue
Block a user