xynthe 0.1.0

A unified orchestration framework for autonomous intelligence with temporal continuity
Documentation
//! Execution tracing for behavioral determinism and replay
//!
//! Trace module provides immutable execution traces that record every cognitive
//! state transition, tool invocation, and memory update as cryptographically
//! verifiable events.

use crate::capability_binding::CapabilityTrace;
use crate::thought_stream::ThoughtEvent;
use crate::types::{Timestamp, StructuredContent, ProvenanceChain};
use crate::Result;
use serde::{Deserialize, Serialize};
use std::sync::Arc;
use tokio::sync::RwLock;
use tracing;
use uuid::Uuid;

/// Type of trace event
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(tag = "type")]
pub enum TraceEvent {
    /// Thought stream event
    Thought(ThoughtEvent),

    /// Capability invocation event
    Capability(CapabilityTrace),

    /// Context fabric access event
    ContextAccess {
        operation: String,
        query: String,
        timestamp: Timestamp,
        duration_ms: u64,
    },

    /// Execution state change
    ExecutionState {
        from: String,
        to: String,
        timestamp: Timestamp,
        metadata: serde_json::Value,
    },

    /// User intervention
    UserIntervention {
        action: String,
        reason: String,
        timestamp: Timestamp,
    },
}

impl TraceEvent {
    /// Get the timestamp of this event
    pub fn timestamp(&self) -> Timestamp {
        match self {
            Self::Thought(event) => event.timestamp,
            Self::Capability(trace) => trace.metadata.timestamp,
            Self::ContextAccess { timestamp, .. } => *timestamp,
            Self::ExecutionState { timestamp, .. } => *timestamp,
            Self::UserIntervention { timestamp, .. } => *timestamp,
        }
    }

    /// Check if this event represents a failure
    pub fn is_failure(&self) -> bool {
        match self {
            Self::Capability(trace) => !trace.success,
            _ => false,
        }
    }
}

/// Immutable trace of execution
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ExecutionTrace {
    /// Unique trace identifier
    pub id: Uuid,

    /// Trace creation timestamp
    pub created_at: Timestamp,

    /// Agent identifier
    pub agent_id: String,

    /// Sequence of events
    pub events: Vec<TraceEvent>,

    /// Metadata about the execution
    pub metadata: TraceMetadata,
}

impl ExecutionTrace {
    /// Create a new execution trace
    pub fn new(agent_id: impl Into<String>) -> Self {
        Self {
            id: Uuid::new_v4(),
            created_at: Timestamp::now(),
            agent_id: agent_id.into(),
            events: Vec::new(),
            metadata: TraceMetadata::default(),
        }
    }

    /// Add an event to the trace
    pub fn add_event(&mut self, event: TraceEvent) {
        self.events.push(event);
    }

    /// Get the number of events
    pub fn len(&self) -> usize {
        self.events.len()
    }

    /// Check if the trace is empty
    pub fn is_empty(&self) -> bool {
        self.events.is_empty()
    }

    /// Filter events by type
    pub fn filter_events(&self, predicate: fn(&TraceEvent) -> bool) -> Vec<&TraceEvent> {
        self.events.iter().filter(|e| predicate(e)).collect()
    }

    /// Get all thought events
    pub fn thought_events(&self) -> Vec<&ThoughtEvent> {
        self.events
            .iter()
            .filter_map(|e| match e {
                TraceEvent::Thought(event) => Some(event),
                _ => None,
            })
            .collect()
    }

    /// Get all capability traces
    pub fn capability_traces(&self) -> Vec<&CapabilityTrace> {
        self.events
            .iter()
            .filter_map(|e| match e {
                TraceEvent::Capability(trace) => Some(trace),
                _ => None,
            })
            .collect()
    }

    /// Check if execution completed successfully
    pub fn is_successful(&self) -> bool {
        !self.events.iter().any(|e| e.is_failure())
    }

    /// Get duration of execution from start to end
    pub fn duration(&self) -> Option<std::time::Duration> {
        if self.events.is_empty() {
            return None;
        }

        let first = self.events.first()?.timestamp();
        let last = self.events.last()?.timestamp();

        Some(last.as_datetime().signed_duration_since(first.as_datetime()).to_std().ok()?)
    }
}

/// Metadata about an execution trace
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct TraceMetadata {
    /// Total number of events
    pub event_count: usize,

    /// Number of failed invocations
    pub failure_count: usize,

    /// Resource usage statistics
    pub resource_usage: serde_json::Value,

    /// Additional custom metadata
    pub custom: serde_json::Value,
}

/// Trace recorder for capturing execution
#[derive(Clone)]
pub struct TraceRecorder {
    /// Current execution trace
    trace: Arc<RwLock<ExecutionTrace>>,

    /// All recorded traces
    archive: Arc<RwLock<Vec<ExecutionTrace>>>,
}

impl TraceRecorder {
    /// Create a new trace recorder
    pub fn new(agent_id: impl Into<String>) -> Self {
        Self {
            trace: Arc::new(RwLock::new(ExecutionTrace::new(agent_id))),
            archive: Arc::new(RwLock::new(Vec::new())),
        }
    }

    /// Record an event
    pub async fn record(&self, event: TraceEvent) {
        let mut trace = self.trace.write().await;
        trace.add_event(event);
    }

    /// Record a thought event
    pub async fn record_thought(&self, event: ThoughtEvent) {
        self.record(TraceEvent::Thought(event)).await;
    }

    /// Record a capability trace
    pub async fn record_capability(&self, trace: CapabilityTrace) {
        self.record(TraceEvent::Capability(trace)).await;
    }

    /// Get the current trace
    pub async fn current_trace(&self) -> ExecutionTrace {
        let trace = self.trace.read().await;
        trace.clone()
    }

    /// Finalize the current trace and start a new one
    pub async fn finalize(&self) -> ExecutionTrace {
        let completed = {
            let mut trace = self.trace.write().await;
            let mut completed = trace.clone();

            // Update metadata
            completed.metadata.event_count = completed.len();
            completed.metadata.failure_count = completed
                .events
                .iter()
                .filter(|e| e.is_failure())
                .count();

            // Archive the completed trace
            let mut archive = self.archive.write().await;
            archive.push(completed.clone());

            completed
        };

        // Start a new trace
        let agent_id = completed.agent_id.clone();
        *self.trace.write().await = ExecutionTrace::new(agent_id);

        completed
    }

    /// Get all archived traces
    pub async fn archive(&self) -> Vec<ExecutionTrace> {
        let archive = self.archive.read().await;
        archive.clone()
    }

    /// Clear the archive
    pub async fn clear_archive(&self) {
        let mut archive = self.archive.write().await;
        archive.clear();
    }

    /// Replay a trace (without side effects)
    pub async fn replay(trace: &ExecutionTrace) -> Result<()> {
        tracing::info!("Replaying trace {} with {} events", trace.id, trace.len());

        for (idx, event) in trace.events.iter().enumerate() {
            tracing::debug!("Replaying event {}: {:?}", idx, event);

            // In a real implementation, we would replay the actual logic
            // For now, we just validate the trace structure
            match event {
                TraceEvent::Thought(thought) => {
                    tracing::debug!("Thought event: {:?}", thought.event_type);
                }
                TraceEvent::Capability(cap) => {
                    tracing::debug!("Capability event: {}", cap.capability_name);
                    if !cap.success {
                        return Err(crate::Error::ExecutionError("Failed capability in replay".into()));
                    }
                }
                TraceEvent::ContextAccess { operation, .. } => {
                    tracing::debug!("Context access: {}", operation);
                }
                TraceEvent::ExecutionState { from, to, .. } => {
                    tracing::debug!("State transition: {} -> {}", from, to);
                }
                TraceEvent::UserIntervention { action, .. } => {
                    tracing::info!("User intervention during replay: {}", action);
                }
            }
        }

        Ok(())
    }
}

impl Default for TraceRecorder {
    fn default() -> Self {
        Self::new("default")
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_execution_trace_creation() {
        let trace = ExecutionTrace::new("test_agent");
        assert_eq!(trace.agent_id, "test_agent");
        assert!(trace.is_empty());
    }

    #[tokio::test]
    async fn test_trace_recorder() {
        let recorder = TraceRecorder::new("test_agent");

        let thought = ThoughtEvent::observation(
            StructuredContent::text("test observation"),
            ProvenanceChain::new(),
        );

        recorder.record_thought(thought).await;

        let trace = recorder.current_trace().await;
        assert_eq!(trace.len(), 1);

        let events = trace.thought_events();
        assert_eq!(events.len(), 1);
    }

    #[test]
    fn test_trace_duration() {
        let mut trace = ExecutionTrace::new("test");

        // Add some events with time progression
        trace.add_event(TraceEvent::ContextAccess {
            operation: "test".into(),
            query: "test".into(),
            timestamp: Timestamp::now(),
            duration_ms: 0,
        });

        let duration = trace.duration();
        assert!(duration.is_some());
        assert_eq!(duration.unwrap().as_millis(), 0); // Should be very quick
    }
}