feat: Phase 5.3 - Multi-Agent Learning Infrastructure
Implement intelligent agent learning from Knowledge Graph execution history
with per-task-type expertise tracking, recency bias, and learning curves.
## Phase 5.3 Implementation
### Learning Infrastructure (✅ Complete)
- LearningProfileService with per-task-type expertise metrics
- TaskTypeExpertise model tracking success_rate, confidence, learning curves
- Recency bias weighting: recent 7 days weighted 3x higher (exponential decay)
- Confidence scoring prevents overfitting: min(1.0, executions / 20)
- Learning curves computed from daily execution windows
### Agent Scoring Service (✅ Complete)
- Unified AgentScore combining SwarmCoordinator + learning profiles
- Scoring formula: 0.3*base + 0.5*expertise + 0.2*confidence
- Rank agents by combined score for intelligent assignment
- Support for recency-biased scoring (recent_success_rate)
- Methods: rank_agents, select_best, rank_agents_with_recency
### KG Integration (✅ Complete)
- KGPersistence::get_executions_for_task_type() - query by agent + task type
- KGPersistence::get_agent_executions() - all executions for agent
- Coordinator::load_learning_profile_from_kg() - core KG→Learning integration
- Coordinator::load_all_learning_profiles() - batch load for multiple agents
- Convert PersistedExecution → ExecutionData for learning calculations
### Agent Assignment Integration (✅ Complete)
- AgentCoordinator uses learning profiles for task assignment
- extract_task_type() infers task type from title/description
- assign_task() scores candidates using AgentScoringService
- Fallback to load-based selection if no learning data available
- Learning profiles stored in coordinator.learning_profiles RwLock
### Profile Adapter Enhancements (✅ Complete)
- create_learning_profile() - initialize empty profiles
- add_task_type_expertise() - set task-type expertise
- update_profile_with_learning() - update swarm profiles from learning
## Files Modified
### vapora-knowledge-graph/src/persistence.rs (+30 lines)
- get_executions_for_task_type(agent_id, task_type, limit)
- get_agent_executions(agent_id, limit)
### vapora-agents/src/coordinator.rs (+100 lines)
- load_learning_profile_from_kg() - core KG integration method
- load_all_learning_profiles() - batch loading for agents
- assign_task() already uses learning-based scoring via AgentScoringService
### Existing Complete Implementation
- vapora-knowledge-graph/src/learning.rs - calculation functions
- vapora-agents/src/learning_profile.rs - data structures and expertise
- vapora-agents/src/scoring.rs - unified scoring service
- vapora-agents/src/profile_adapter.rs - adapter methods
## Tests Passing
- learning_profile: 7 tests ✅
- scoring: 5 tests ✅
- profile_adapter: 6 tests ✅
- coordinator: learning-specific tests ✅
## Data Flow
1. Task arrives → AgentCoordinator::assign_task()
2. Extract task_type from description
3. Query KG for task-type executions (load_learning_profile_from_kg)
4. Calculate expertise with recency bias
5. Score candidates (SwarmCoordinator + learning)
6. Assign to top-scored agent
7. Execution result → KG → Update learning profiles
## Key Design Decisions
✅ Recency bias: 7-day half-life with 3x weight for recent performance
✅ Confidence scoring: min(1.0, total_executions / 20) prevents overfitting
✅ Hierarchical scoring: 30% base load, 50% expertise, 20% confidence
✅ KG query limit: 100 recent executions per task-type for performance
✅ Async loading: load_learning_profile_from_kg supports concurrent loads
## Next: Phase 5.4 - Cost Optimization
Ready to implement budget enforcement and cost-aware provider selection.
2026-01-11 13:03:53 +00:00
|
|
|
// Core domain models for VAPORA v1.0
|
|
|
|
|
// Phase 1: Complete type definitions for backend
|
|
|
|
|
|
|
|
|
|
use chrono::{DateTime, Utc};
|
|
|
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
|
|
|
|
|
|
// ============================================================================
|
|
|
|
|
// Project Models
|
|
|
|
|
// ============================================================================
|
|
|
|
|
|
|
|
|
|
/// Project model
|
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
|
|
|
|
pub struct Project {
|
|
|
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
|
|
|
pub id: Option<String>,
|
|
|
|
|
pub tenant_id: String,
|
|
|
|
|
pub title: String,
|
|
|
|
|
pub description: Option<String>,
|
|
|
|
|
pub status: ProjectStatus,
|
|
|
|
|
#[serde(default)]
|
|
|
|
|
pub features: Vec<String>,
|
|
|
|
|
pub created_at: DateTime<Utc>,
|
|
|
|
|
pub updated_at: DateTime<Utc>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Project status enumeration
|
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
|
|
|
|
#[serde(rename_all = "lowercase")]
|
|
|
|
|
pub enum ProjectStatus {
|
|
|
|
|
Active,
|
|
|
|
|
Archived,
|
|
|
|
|
Completed,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ============================================================================
|
|
|
|
|
// Task Models
|
|
|
|
|
// ============================================================================
|
|
|
|
|
|
|
|
|
|
/// Task model for Kanban board
|
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
|
|
|
|
pub struct Task {
|
|
|
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
|
|
|
pub id: Option<String>,
|
|
|
|
|
pub tenant_id: String,
|
|
|
|
|
pub project_id: String,
|
|
|
|
|
pub title: String,
|
|
|
|
|
pub description: Option<String>,
|
|
|
|
|
pub status: TaskStatus,
|
|
|
|
|
pub assignee: String,
|
|
|
|
|
pub priority: TaskPriority,
|
|
|
|
|
pub task_order: i32,
|
|
|
|
|
pub feature: Option<String>,
|
|
|
|
|
pub created_at: DateTime<Utc>,
|
|
|
|
|
pub updated_at: DateTime<Utc>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Task status for Kanban columns
|
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
|
|
|
|
#[serde(rename_all = "lowercase")]
|
|
|
|
|
pub enum TaskStatus {
|
|
|
|
|
Todo,
|
|
|
|
|
Doing,
|
|
|
|
|
Review,
|
|
|
|
|
Done,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Task priority levels
|
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
|
|
|
|
|
#[serde(rename_all = "lowercase")]
|
|
|
|
|
pub enum TaskPriority {
|
|
|
|
|
Low,
|
|
|
|
|
Medium,
|
|
|
|
|
High,
|
|
|
|
|
Critical,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ============================================================================
|
|
|
|
|
// Agent Models
|
|
|
|
|
// ============================================================================
|
|
|
|
|
|
|
|
|
|
/// Agent registry model (12 specialized roles)
|
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
|
|
|
|
pub struct Agent {
|
|
|
|
|
pub id: String,
|
|
|
|
|
pub role: AgentRole,
|
|
|
|
|
pub name: String,
|
|
|
|
|
pub version: String,
|
|
|
|
|
pub status: AgentStatus,
|
|
|
|
|
#[serde(default)]
|
|
|
|
|
pub capabilities: Vec<String>,
|
|
|
|
|
#[serde(default)]
|
|
|
|
|
pub skills: Vec<String>,
|
|
|
|
|
pub llm_provider: String,
|
|
|
|
|
pub llm_model: String,
|
|
|
|
|
pub max_concurrent_tasks: u32,
|
|
|
|
|
pub created_at: DateTime<Utc>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Agent role enumeration (12 roles as per VAPORA spec)
|
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
|
|
|
|
#[serde(rename_all = "snake_case")]
|
|
|
|
|
pub enum AgentRole {
|
|
|
|
|
Architect,
|
|
|
|
|
Developer,
|
|
|
|
|
CodeReviewer,
|
|
|
|
|
Tester,
|
|
|
|
|
Documenter,
|
|
|
|
|
Marketer,
|
|
|
|
|
Presenter,
|
|
|
|
|
DevOps,
|
|
|
|
|
Monitor,
|
|
|
|
|
Security,
|
|
|
|
|
ProjectManager,
|
|
|
|
|
DecisionMaker,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Agent status
|
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
|
|
|
|
#[serde(rename_all = "lowercase")]
|
|
|
|
|
pub enum AgentStatus {
|
|
|
|
|
Active,
|
|
|
|
|
Inactive,
|
|
|
|
|
Updating,
|
|
|
|
|
Error,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Agent instance (runtime pod)
|
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
|
|
|
pub struct AgentInstance {
|
|
|
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
|
|
|
pub id: Option<String>,
|
|
|
|
|
pub agent_id: String,
|
|
|
|
|
pub pod_id: String,
|
|
|
|
|
pub ip: Option<String>,
|
|
|
|
|
pub port: u16,
|
|
|
|
|
pub start_time: DateTime<Utc>,
|
|
|
|
|
pub last_heartbeat: DateTime<Utc>,
|
|
|
|
|
pub tasks_completed: u32,
|
|
|
|
|
pub uptime_percentage: f64,
|
|
|
|
|
pub status: AgentInstanceStatus,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Agent instance status
|
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
|
|
|
|
#[serde(rename_all = "lowercase")]
|
|
|
|
|
pub enum AgentInstanceStatus {
|
|
|
|
|
Running,
|
|
|
|
|
Stopped,
|
|
|
|
|
Error,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ============================================================================
|
|
|
|
|
// User Models
|
|
|
|
|
// ============================================================================
|
|
|
|
|
|
|
|
|
|
/// User model
|
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
|
|
|
pub struct User {
|
|
|
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
|
|
|
pub id: Option<String>,
|
|
|
|
|
pub email: String,
|
|
|
|
|
pub username: String,
|
|
|
|
|
#[serde(skip_serializing)]
|
|
|
|
|
pub password_hash: String,
|
|
|
|
|
#[serde(default)]
|
|
|
|
|
pub roles: Vec<String>,
|
|
|
|
|
pub created_at: DateTime<Utc>,
|
|
|
|
|
pub updated_at: DateTime<Utc>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ============================================================================
|
|
|
|
|
// Workflow Models
|
|
|
|
|
// ============================================================================
|
|
|
|
|
|
|
|
|
|
/// Workflow definition
|
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
|
|
|
|
pub struct Workflow {
|
|
|
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
|
|
|
pub id: Option<String>,
|
|
|
|
|
pub tenant_id: String,
|
|
|
|
|
pub name: String,
|
|
|
|
|
pub description: Option<String>,
|
|
|
|
|
pub status: WorkflowStatus,
|
|
|
|
|
pub definition: serde_json::Value,
|
|
|
|
|
pub created_at: DateTime<Utc>,
|
|
|
|
|
pub updated_at: DateTime<Utc>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Workflow status
|
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
|
|
|
|
#[serde(rename_all = "lowercase")]
|
|
|
|
|
pub enum WorkflowStatus {
|
|
|
|
|
Draft,
|
|
|
|
|
Active,
|
|
|
|
|
Paused,
|
|
|
|
|
Completed,
|
|
|
|
|
Failed,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Workflow step execution
|
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
|
|
|
pub struct WorkflowStep {
|
|
|
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
|
|
|
pub id: Option<String>,
|
|
|
|
|
pub workflow_id: String,
|
|
|
|
|
pub step_id: String,
|
|
|
|
|
pub step_name: String,
|
|
|
|
|
pub agent_id: Option<String>,
|
|
|
|
|
pub status: WorkflowStepStatus,
|
|
|
|
|
pub result: Option<serde_json::Value>,
|
|
|
|
|
pub error_message: Option<String>,
|
|
|
|
|
pub started_at: Option<DateTime<Utc>>,
|
|
|
|
|
pub completed_at: Option<DateTime<Utc>>,
|
|
|
|
|
pub created_at: DateTime<Utc>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Workflow step status
|
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
|
|
|
|
#[serde(rename_all = "snake_case")]
|
|
|
|
|
pub enum WorkflowStepStatus {
|
|
|
|
|
Pending,
|
|
|
|
|
InProgress,
|
|
|
|
|
Completed,
|
|
|
|
|
Failed,
|
|
|
|
|
Skipped,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ============================================================================
|
|
|
|
|
// Document Models (RAG)
|
|
|
|
|
// ============================================================================
|
|
|
|
|
|
|
|
|
|
/// Document for RAG system
|
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
|
|
|
pub struct Document {
|
|
|
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
|
|
|
pub id: Option<String>,
|
|
|
|
|
pub tenant_id: String,
|
|
|
|
|
pub project_id: Option<String>,
|
|
|
|
|
pub title: String,
|
|
|
|
|
pub content: String,
|
|
|
|
|
pub content_type: DocumentContentType,
|
|
|
|
|
#[serde(default)]
|
|
|
|
|
pub metadata: serde_json::Value,
|
|
|
|
|
pub embedding: Option<Vec<f32>>,
|
|
|
|
|
pub source_path: Option<String>,
|
|
|
|
|
#[serde(default)]
|
|
|
|
|
pub tags: Vec<String>,
|
|
|
|
|
pub created_at: DateTime<Utc>,
|
|
|
|
|
pub updated_at: DateTime<Utc>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Document content type
|
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
|
|
|
|
#[serde(rename_all = "lowercase")]
|
|
|
|
|
pub enum DocumentContentType {
|
|
|
|
|
Markdown,
|
|
|
|
|
Code,
|
|
|
|
|
Text,
|
|
|
|
|
Json,
|
|
|
|
|
}
|
2026-02-03 21:35:00 +00:00
|
|
|
|
|
|
|
|
// ============================================================================
|
|
|
|
|
// Proposal Models (Risk-Based Approval)
|
|
|
|
|
// ============================================================================
|
|
|
|
|
|
|
|
|
|
/// Proposal model for task approval gates
|
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
|
|
|
|
pub struct Proposal {
|
|
|
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
|
|
|
pub id: Option<String>,
|
|
|
|
|
pub tenant_id: String,
|
|
|
|
|
pub project_id: String,
|
|
|
|
|
pub task_id: String,
|
|
|
|
|
pub agent_id: String,
|
|
|
|
|
pub title: String,
|
|
|
|
|
pub description: Option<String>,
|
|
|
|
|
pub status: ProposalStatus,
|
|
|
|
|
pub risk_level: RiskLevel,
|
|
|
|
|
pub plan_details: PlanDetails,
|
|
|
|
|
pub created_at: DateTime<Utc>,
|
|
|
|
|
pub submitted_at: Option<DateTime<Utc>>,
|
|
|
|
|
pub reviewed_at: Option<DateTime<Utc>>,
|
|
|
|
|
pub executed_at: Option<DateTime<Utc>>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Proposal status enumeration
|
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
|
|
|
|
#[serde(rename_all = "lowercase")]
|
|
|
|
|
pub enum ProposalStatus {
|
|
|
|
|
Proposed,
|
|
|
|
|
Approved,
|
|
|
|
|
Rejected,
|
|
|
|
|
Executed,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Risk level for proposals
|
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
|
|
|
|
|
#[serde(rename_all = "lowercase")]
|
|
|
|
|
pub enum RiskLevel {
|
|
|
|
|
Low,
|
|
|
|
|
Medium,
|
|
|
|
|
High,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Detailed plan information for proposals
|
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
|
|
|
|
pub struct PlanDetails {
|
|
|
|
|
pub description: String,
|
|
|
|
|
pub affected_resources: Vec<String>,
|
|
|
|
|
pub estimated_cost: Option<f64>,
|
|
|
|
|
pub confidence: f64,
|
|
|
|
|
pub rollback_strategy: Option<String>,
|
|
|
|
|
pub metadata: serde_json::Value,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Review feedback for proposals
|
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
|
|
|
|
pub struct ProposalReview {
|
|
|
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
|
|
|
pub id: Option<String>,
|
|
|
|
|
pub proposal_id: String,
|
|
|
|
|
pub reviewer_id: String,
|
|
|
|
|
pub feedback: String,
|
|
|
|
|
pub approved: bool,
|
|
|
|
|
pub created_at: DateTime<Utc>,
|
|
|
|
|
}
|