# ADR-019: Temporal Execution History con Daily Windowing **Status**: Accepted | Implemented **Date**: 2024-11-01 **Deciders**: Knowledge Graph Team **Technical Story**: Tracking agent execution history with daily aggregation for learning curves --- ## Decision Implementar **temporal execution history** con daily windowed aggregations para computar learning curves. --- ## Rationale 1. **Learning Curves**: Daily aggregations permiten ver trends (improving/stable/declining) 2. **Causal Reasoning**: Histórico permite rastrear problemas a raíz 3. **Temporal Analysis**: Comparer performance across days/weeks 4. **Efficient Queries**: Daily windows permiten group-by queries eficientes --- ## Alternatives Considered ### ❌ Per-Execution Only (No Aggregation) - **Pros**: Maximum detail - **Cons**: Queries slow, hard to identify trends ### ❌ Monthly Aggregation Only - **Pros**: Compact - **Cons**: Misses weekly trends, loses detail ### ✅ Daily Windows (CHOSEN) - Good balance: detail + trend visibility --- ## Trade-offs **Pros**: - ✅ Trends visible at daily granularity - ✅ Learning curves computable - ✅ Efficient aggregation queries - ✅ Retention policy compatible **Cons**: - ⚠️ Storage overhead (daily windows) - ⚠️ Intra-day trends hidden (needs hourly for detail) - ⚠️ Rollup complexity --- ## Implementation **Execution Record Model**: ```rust // crates/vapora-knowledge-graph/src/models.rs pub struct ExecutionRecord { pub id: String, pub agent_id: String, pub task_id: String, pub task_type: String, pub success: bool, pub quality_score: f32, pub latency_ms: u32, pub cost_cents: u32, pub timestamp: DateTime, pub daily_window: String, // YYYY-MM-DD } pub struct DailyAggregation { pub id: String, pub agent_id: String, pub task_type: String, pub day: String, // YYYY-MM-DD pub execution_count: u32, pub success_count: u32, pub success_rate: f32, pub avg_quality: f32, pub avg_latency_ms: f32, pub total_cost_cents: u32, pub trend: TrendDirection, // Improving, Stable, Declining } #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum TrendDirection { Improving, Stable, Declining, } ``` **Recording Execution**: ```rust pub async fn record_execution( db: &Surreal, record: ExecutionRecord, ) -> Result { // Set daily_window automatically let mut record = record; record.daily_window = record.timestamp.format("%Y-%m-%d").to_string(); // Insert execution record let id = db .create("executions") .content(&record) .await? .id .unwrap(); // Trigger daily aggregation (async) tokio::spawn(aggregate_daily_window( db.clone(), record.agent_id.clone(), record.task_type.clone(), record.daily_window.clone(), )); Ok(id) } ``` **Daily Aggregation**: ```rust pub async fn aggregate_daily_window( db: Surreal, agent_id: String, task_type: String, day: String, ) -> Result<()> { // Query all executions for this day/agent/tasktype let executions = db .query( "SELECT * FROM executions \ WHERE agent_id = $1 AND task_type = $2 AND daily_window = $3" ) .bind((&agent_id, &task_type, &day)) .await? .take::>(0)? .unwrap_or_default(); if executions.is_empty() { return Ok(()); } // Compute aggregates let execution_count = executions.len() as u32; let success_count = executions.iter().filter(|e| e.success).count() as u32; let success_rate = success_count as f32 / execution_count as f32; let avg_quality: f32 = executions.iter().map(|e| e.quality_score).sum::() / execution_count as f32; let avg_latency_ms: f32 = executions.iter().map(|e| e.latency_ms as f32).sum::() / execution_count as f32; let total_cost_cents: u32 = executions.iter().map(|e| e.cost_cents).sum(); // Compute trend (compare to yesterday) let yesterday = (chrono::NaiveDate::parse_from_str(&day, "%Y-%m-%d")? - chrono::Duration::days(1)) .format("%Y-%m-%d") .to_string(); let yesterday_agg = db .query( "SELECT success_rate FROM daily_aggregations \ WHERE agent_id = $1 AND task_type = $2 AND day = $3" ) .bind((&agent_id, &task_type, &yesterday)) .await? .take::>(0)?; let trend = if let Some(prev) = yesterday_agg.first() { let change = success_rate - prev.success_rate; if change > 0.05 { TrendDirection::Improving } else if change < -0.05 { TrendDirection::Declining } else { TrendDirection::Stable } } else { TrendDirection::Stable }; // Create or update aggregation record let agg = DailyAggregation { id: format!("{}-{}-{}", &agent_id, &task_type, &day), agent_id, task_type, day, execution_count, success_count, success_rate, avg_quality, avg_latency_ms, total_cost_cents, trend, }; db.upsert(&agg.id).content(&agg).await?; Ok(()) } ``` **Learning Curve Query**: ```rust pub async fn get_learning_curve( db: &Surreal, agent_id: &str, task_type: &str, days: u32, ) -> Result> { let since = (Utc::now() - chrono::Duration::days(days as i64)) .format("%Y-%m-%d") .to_string(); let curve = db .query( "SELECT * FROM daily_aggregations \ WHERE agent_id = $1 AND task_type = $2 AND day >= $3 \ ORDER BY day ASC" ) .bind((agent_id, task_type, since)) .await? .take::>(0)? .unwrap_or_default(); Ok(curve) } ``` **Trend Analysis**: ```rust pub fn analyze_trend(curve: &[DailyAggregation]) -> TrendAnalysis { if curve.len() < 2 { return TrendAnalysis::InsufficientData; } let improving_days = curve.iter().filter(|d| d.trend == TrendDirection::Improving).count(); let declining_days = curve.iter().filter(|d| d.trend == TrendDirection::Declining).count(); if improving_days > declining_days { TrendAnalysis::Improving } else if declining_days > improving_days { TrendAnalysis::Declining } else { TrendAnalysis::Stable } } ``` **Key Files**: - `/crates/vapora-knowledge-graph/src/models.rs` (models) - `/crates/vapora-knowledge-graph/src/aggregation.rs` (daily aggregation) - `/crates/vapora-knowledge-graph/src/learning.rs` (learning curves) --- ## Verification ```bash # Test execution recording with daily window cargo test -p vapora-knowledge-graph test_execution_recording # Test daily aggregation cargo test -p vapora-knowledge-graph test_daily_aggregation # Test learning curve computation (7 days) cargo test -p vapora-knowledge-graph test_learning_curve_7day # Test trend detection cargo test -p vapora-knowledge-graph test_trend_detection # Integration: full lifecycle cargo test -p vapora-knowledge-graph test_temporal_history_lifecycle ``` **Expected Output**: - Executions recorded with daily_window set - Daily aggregations computed correctly - Learning curves show trends - Trends detected accurately (improving/stable/declining) - Queries efficient with daily windows --- ## Consequences ### Data Retention - Daily aggregations permanent (minimal storage) - Individual execution records archived after 30 days - Trend analysis available indefinitely ### Trend Visibility - Daily trends visible immediately - Week-over-week comparisons possible - Month-over-month trends computable ### Performance - Aggregation queries use indexes (efficient) - Daily rollup automatic (background task) - No real-time overhead ### Monitoring - Trends inform agent selection decisions - Declining agents flagged for investigation - Improving agents promoted --- ## References - `/crates/vapora-knowledge-graph/src/aggregation.rs` (implementation) - `/crates/vapora-knowledge-graph/src/learning.rs` (usage) - ADR-013 (Knowledge Graph) - ADR-014 (Learning Profiles) --- **Related ADRs**: ADR-013 (Knowledge Graph), ADR-014 (Learning Profiles), ADR-020 (Audit Trail)