feat: vendor midstream and sublinear-time-solver libraries (#109)

Add ruvnet/midstream (AIMDS real-time inference) and
ruvnet/sublinear-time-solver (sublinear optimization algorithms)
as vendored dependencies under vendor/.
This commit is contained in:
rUv
2026-03-02 23:34:05 -05:00
committed by GitHub
parent 14902e6b4e
commit 407b46b206
1600 changed files with 1852646 additions and 0 deletions
+63
View File
@@ -0,0 +1,63 @@
use midstream::{Midstream, HyprSettings, HyprServiceImpl, StreamProcessor, LLMClient};
use futures::stream::BoxStream;
use futures::stream::iter;
use std::time::Duration;
// Example LLM client implementation
struct ExampleLLMClient;
impl LLMClient for ExampleLLMClient {
fn stream(&self) -> BoxStream<'static, String> {
Box::pin(iter(vec![
"URGENT: What's the weather like?".to_string(),
"Schedule a meeting for tomorrow".to_string(),
"Just a normal message".to_string(),
]))
}
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Initialize settings
let settings = HyprSettings::new()?;
// Create hyprstream service
let hypr_service = HyprServiceImpl::new(&settings).await?;
// Create LLM client
let llm_client = ExampleLLMClient;
// Initialize Midstream
let midstream = Midstream::new(
Box::new(llm_client),
Box::new(hypr_service),
);
// Process stream
let messages = midstream.process_stream().await?;
println!("\nProcessed messages:");
for msg in &messages {
println!("- Content: {}", msg.content);
println!(" Intent: {:?}", msg.intent);
if let Some(response) = &msg.tool_response {
println!(" Tool Response: {}", response);
}
println!();
}
// Get metrics
let metrics = midstream.get_metrics().await;
println!("\nCollected metrics:");
for metric in &metrics {
println!("- Name: {}", metric.name);
println!(" Value: {}", metric.value);
println!(" Labels: {:?}", metric.labels);
println!();
}
// Get average sentiment for last 5 minutes
let avg = midstream.get_average_sentiment(Duration::from_secs(300)).await?;
println!("\nAverage sentiment: {}", avg);
Ok(())
}
+86
View File
@@ -0,0 +1,86 @@
use config::{Config, ConfigError, Environment, File};
use serde::Deserialize;
use std::path::Path;
#[derive(Debug, Deserialize)]
pub struct HyprSettings {
pub engine: EngineSettings,
pub cache: CacheSettings,
}
#[derive(Debug, Deserialize)]
pub struct EngineSettings {
pub engine: String,
pub connection: String,
pub options: std::collections::HashMap<String, String>,
}
#[derive(Debug, Deserialize)]
pub struct CacheSettings {
pub enabled: bool,
pub engine: String,
pub connection: String,
pub max_duration_secs: u64,
}
impl HyprSettings {
pub fn new() -> Result<Self, ConfigError> {
let config_dir = Path::new("config");
let builder = Config::builder()
// Start with default settings
.add_source(File::from(config_dir.join("default.toml")).required(false))
// Add local overrides
.add_source(File::from(config_dir.join("local.toml")).required(false))
// Add environment variables with prefix MIDSTREAM_
.add_source(Environment::with_prefix("MIDSTREAM").separator("_"));
builder.build()?.try_deserialize()
}
}
impl Default for HyprSettings {
fn default() -> Self {
Self {
engine: EngineSettings {
engine: "duckdb".to_string(),
connection: ":memory:".to_string(),
options: std::collections::HashMap::new(),
},
cache: CacheSettings {
enabled: true,
engine: "duckdb".to_string(),
connection: ":memory:".to_string(),
max_duration_secs: 3600,
},
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::Once;
static INIT: Once = Once::new();
fn setup() {
INIT.call_once(|| {
std::env::set_var("MIDSTREAM_ENGINE_ENGINE", "test_engine");
});
}
#[test]
fn test_default_settings() {
let settings = HyprSettings::default();
assert_eq!(settings.engine.engine, "duckdb");
assert!(settings.cache.enabled);
}
#[test]
fn test_environment_override() {
setup();
let settings = HyprSettings::new().unwrap();
assert_eq!(settings.engine.engine, "test_engine");
}
}
+115
View File
@@ -0,0 +1,115 @@
use crate::config::HyprSettings;
use crate::midstream::{HyprService, MetricRecord, TimeWindow, AggregateFunction};
use std::sync::Arc;
use tokio::sync::Mutex;
use async_trait::async_trait;
type BoxError = Box<dyn std::error::Error>;
pub struct HyprServiceImpl {
metrics: Arc<Mutex<Vec<MetricRecord>>>,
}
impl HyprServiceImpl {
pub async fn new(_settings: &HyprSettings) -> Result<Self, BoxError> {
Ok(Self {
metrics: Arc::new(Mutex::new(Vec::new())),
})
}
async fn calculate_aggregate(&self, window: TimeWindow, func: AggregateFunction) -> Result<f64, BoxError> {
let metrics = self.metrics.lock().await;
let now = chrono::Utc::now().timestamp() as u64;
let window_secs = match window {
TimeWindow::Minutes(m) => m as u64 * 60,
TimeWindow::Hours(h) => h as u64 * 3600,
TimeWindow::Days(d) => d as u64 * 86400,
};
let filtered: Vec<_> = metrics
.iter()
.filter(|m| now - m.timestamp <= window_secs)
.collect();
match func {
AggregateFunction::Average => {
if filtered.is_empty() {
Ok(0.0)
} else {
let sum: f64 = filtered.iter().map(|m| m.value).sum();
Ok(sum / filtered.len() as f64)
}
}
AggregateFunction::Sum => {
Ok(filtered.iter().map(|m| m.value).sum())
}
AggregateFunction::Count => {
Ok(filtered.len() as f64)
}
}
}
}
#[async_trait]
impl HyprService for HyprServiceImpl {
async fn ingest_metric(&self, metric: MetricRecord) -> Result<(), BoxError> {
let mut metrics = self.metrics.lock().await;
metrics.push(metric);
Ok(())
}
async fn query_aggregate(&self, window: TimeWindow, func: AggregateFunction) -> Result<f64, BoxError> {
self.calculate_aggregate(window, func).await
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test(flavor = "multi_thread")]
async fn test_hypr_service_creation() {
let settings = HyprSettings::default();
let service = HyprServiceImpl::new(&settings).await;
assert!(service.is_ok());
}
#[tokio::test(flavor = "multi_thread")]
async fn test_metric_ingestion() {
let settings = HyprSettings::default();
let service = HyprServiceImpl::new(&settings).await.unwrap();
let metric = MetricRecord {
timestamp: chrono::Utc::now().timestamp() as u64,
name: "test_metric".to_string(),
value: 1.0,
labels: vec![("test".to_string(), "true".to_string())],
};
let result = service.ingest_metric(metric).await;
assert!(result.is_ok());
}
#[tokio::test(flavor = "multi_thread")]
async fn test_aggregation_query() {
let settings = HyprSettings::default();
let service = HyprServiceImpl::new(&settings).await.unwrap();
// First ingest some metrics
let metric = MetricRecord {
timestamp: chrono::Utc::now().timestamp() as u64,
name: "test_metric".to_string(),
value: 1.0,
labels: vec![("test".to_string(), "true".to_string())],
};
service.ingest_metric(metric).await.unwrap();
// Now query the aggregate
let result = service.query_aggregate(
TimeWindow::Minutes(5),
AggregateFunction::Average,
).await;
assert!(result.is_ok());
assert_eq!(result.unwrap(), 1.0);
}
}
+286
View File
@@ -0,0 +1,286 @@
//! Agentic loop for autonomous decision-making (Plan-Act-Observe-Learn)
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use async_trait::async_trait;
use super::types::{Context, AgentState, Goal, Policy, Reward};
use super::LeanAgenticConfig;
/// Agentic loop orchestrator
pub struct AgenticLoop {
/// Current agent state
state: AgentState,
/// Configuration
config: LeanAgenticConfig,
/// Action history
action_history: Vec<Action>,
/// Total reward accumulated
total_reward: f64,
/// Action execution count
action_count: u64,
}
impl AgenticLoop {
pub fn new(config: LeanAgenticConfig) -> Self {
Self {
state: AgentState::default(),
config,
action_history: Vec::new(),
total_reward: 0.0,
action_count: 0,
}
}
/// Plan phase: Generate a plan based on goals and context
pub async fn plan(&self, context: &Context, input: &str) -> Result<Plan, String> {
let mut plan = Plan {
goal: Goal {
id: format!("goal_{}", self.action_count),
description: format!("Process: {}", input),
priority: 1.0,
achieved: false,
},
steps: Vec::new(),
estimated_reward: 0.0,
confidence: 0.0,
};
// Analyze input to determine appropriate actions
let actions = self.generate_action_candidates(input, context).await;
// Rank actions by expected reward
let ranked_actions = self.rank_actions(actions).await;
// Add top actions to plan
for (i, action) in ranked_actions.iter().take(self.config.max_planning_depth).enumerate() {
plan.steps.push(PlanStep {
sequence: i,
action: action.clone(),
preconditions: vec![],
postconditions: vec![],
});
}
plan.estimated_reward = ranked_actions.first()
.map(|a| a.expected_reward)
.unwrap_or(0.0);
plan.confidence = if !plan.steps.is_empty() { 0.8 } else { 0.0 };
Ok(plan)
}
/// Act phase: Select and prepare an action from the plan
pub async fn select_action(&self, plan: &Plan) -> Result<Action, String> {
if plan.steps.is_empty() {
return Err("Empty plan".to_string());
}
// Select first step with highest confidence
let step = &plan.steps[0];
Ok(step.action.clone())
}
/// Execute an action and return observation
pub async fn execute(&mut self, action: &Action) -> Result<Observation, String> {
self.action_count += 1;
self.action_history.push(action.clone());
// Simulate action execution
let observation = Observation {
success: true,
result: format!("Executed: {}", action.action_type),
changes: vec![format!("Action {} completed", action.action_type)],
timestamp: chrono::Utc::now().timestamp(),
};
Ok(observation)
}
/// Compute reward based on observation
pub async fn compute_reward(&self, observation: &Observation) -> Result<Reward, String> {
let base_reward = if observation.success { 1.0 } else { -1.0 };
// Bonus for meaningful changes
let change_bonus = observation.changes.len() as f64 * 0.1;
Ok(base_reward + change_bonus)
}
/// Learn phase: Update policies based on experience
pub async fn learn(&mut self, signal: LearningSignal) -> Result<(), String> {
self.total_reward += signal.reward;
// Update policy based on reward
let policy = Policy {
condition: format!("When: {}", signal.action.description),
action: signal.action.action_type.clone(),
expected_reward: signal.reward,
usage_count: 1,
};
// Check if similar policy exists
if let Some(existing) = self.state.policies.iter_mut()
.find(|p| p.action == policy.action) {
// Update existing policy with exponential moving average
existing.expected_reward = 0.9 * existing.expected_reward + 0.1 * signal.reward;
existing.usage_count += 1;
} else {
// Add new policy
self.state.policies.push(policy);
}
// Update confidence based on learning
self.state.confidence = (self.total_reward / self.action_count as f64).clamp(0.0, 1.0);
Ok(())
}
async fn generate_action_candidates(&self, input: &str, context: &Context) -> Vec<Action> {
let mut candidates = Vec::new();
// Generate different action types based on input
let input_lower = input.to_lowercase();
if input_lower.contains("weather") {
candidates.push(Action {
action_type: "get_weather".to_string(),
description: "Fetch weather information".to_string(),
parameters: HashMap::from([
("query".to_string(), input.to_string()),
]),
tool_calls: vec!["weather_api".to_string()],
expected_outcome: Some("Weather data".to_string()),
expected_reward: 0.8,
});
}
if input_lower.contains("learn") || input_lower.contains("remember") {
candidates.push(Action {
action_type: "update_knowledge".to_string(),
description: "Update knowledge graph".to_string(),
parameters: HashMap::from([
("content".to_string(), input.to_string()),
]),
tool_calls: vec![],
expected_outcome: Some("Knowledge updated".to_string()),
expected_reward: 0.9,
});
}
// Default action: process and respond
candidates.push(Action {
action_type: "process_text".to_string(),
description: format!("Process: {}", input),
parameters: HashMap::from([
("text".to_string(), input.to_string()),
]),
tool_calls: vec![],
expected_outcome: Some("Processed text".to_string()),
expected_reward: 0.5,
});
candidates
}
async fn rank_actions(&self, mut actions: Vec<Action>) -> Vec<Action> {
// Sort by expected reward and learned policies
actions.sort_by(|a, b| {
let a_boost = self.state.policies.iter()
.find(|p| p.action == a.action_type)
.map(|p| p.expected_reward)
.unwrap_or(0.0);
let b_boost = self.state.policies.iter()
.find(|p| p.action == b.action_type)
.map(|p| p.expected_reward)
.unwrap_or(0.0);
let a_score = a.expected_reward + a_boost * 0.5;
let b_score = b.expected_reward + b_boost * 0.5;
b_score.partial_cmp(&a_score).unwrap()
});
actions
}
pub fn action_count(&self) -> u64 {
self.action_count
}
pub fn average_reward(&self) -> f64 {
if self.action_count == 0 {
0.0
} else {
self.total_reward / self.action_count as f64
}
}
}
/// An action the agent can take
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Action {
pub action_type: String,
pub description: String,
pub parameters: HashMap<String, String>,
pub tool_calls: Vec<String>,
pub expected_outcome: Option<String>,
pub expected_reward: f64,
}
/// An observation from the environment
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Observation {
pub success: bool,
pub result: String,
pub changes: Vec<String>,
pub timestamp: i64,
}
/// A plan for achieving a goal
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Plan {
pub goal: Goal,
pub steps: Vec<PlanStep>,
pub estimated_reward: f64,
pub confidence: f64,
}
/// A step in a plan
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PlanStep {
pub sequence: usize,
pub action: Action,
pub preconditions: Vec<String>,
pub postconditions: Vec<String>,
}
/// Learning signal for the agent
#[derive(Debug, Clone)]
pub struct LearningSignal {
pub action: Action,
pub observation: Observation,
pub reward: f64,
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_agentic_loop() {
let config = LeanAgenticConfig::default();
let mut agent = AgenticLoop::new(config);
let context = Context::default();
let plan = agent.plan(&context, "test input").await.unwrap();
assert!(!plan.steps.is_empty());
}
}
+492
View File
@@ -0,0 +1,492 @@
//! Dynamical systems and strange attractor analysis
//!
//! Integrates temporal-attractor-studio for:
//! - Phase space reconstruction
//! - Attractor detection and classification
//! - Stability analysis
//! - Chaos detection via Lyapunov exponents
use nalgebra::{DMatrix, DVector};
use serde::{Deserialize, Serialize};
use std::collections::VecDeque;
/// Types of attractors that can be detected
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum AttractorType {
/// Fixed point (stable equilibrium)
FixedPoint,
/// Limit cycle (periodic oscillation)
LimitCycle,
/// Torus (quasi-periodic)
Torus,
/// Strange attractor (chaotic)
StrangeAttractor,
/// Unknown or transitional
Unknown,
}
/// Phase space point
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PhasePoint {
pub coordinates: Vec<f64>,
pub timestamp: i64,
}
/// Attractor characteristics
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AttractorInfo {
pub attractor_type: AttractorType,
pub lyapunov_exponent: f64,
pub correlation_dimension: f64,
pub is_chaotic: bool,
pub stability_index: f64,
}
/// Phase space trajectory
#[derive(Debug, Clone)]
pub struct Trajectory {
points: Vec<PhasePoint>,
embedding_dimension: usize,
time_delay: usize,
}
impl Trajectory {
/// Create a new trajectory with time-delay embedding
pub fn from_timeseries(
data: &[f64],
embedding_dim: usize,
time_delay: usize,
) -> Self {
let mut points = Vec::new();
// Time-delay embedding (Takens' theorem)
for i in 0..(data.len() - (embedding_dim - 1) * time_delay) {
let mut coords = Vec::with_capacity(embedding_dim);
for j in 0..embedding_dim {
coords.push(data[i + j * time_delay]);
}
points.push(PhasePoint {
coordinates: coords,
timestamp: i as i64,
});
}
Self {
points,
embedding_dimension: embedding_dim,
time_delay,
}
}
/// Get trajectory length
pub fn len(&self) -> usize {
self.points.len()
}
/// Check if trajectory is empty
pub fn is_empty(&self) -> bool {
self.points.is_empty()
}
/// Get embedding dimension
pub fn embedding_dim(&self) -> usize {
self.embedding_dimension
}
}
/// Attractor analyzer using dynamical systems theory
pub struct AttractorAnalyzer {
embedding_dimension: usize,
time_delay: usize,
min_trajectory_length: usize,
lyapunov_iterations: usize,
}
impl AttractorAnalyzer {
/// Create a new attractor analyzer
pub fn new(embedding_dimension: usize, time_delay: usize) -> Self {
Self {
embedding_dimension,
time_delay,
min_trajectory_length: 100,
lyapunov_iterations: 100,
}
}
/// Analyze a time series for attractors
pub fn analyze(&self, data: &[f64]) -> Result<AttractorInfo, String> {
if data.len() < self.min_trajectory_length {
return Err(format!(
"Time series too short: {} < {}",
data.len(),
self.min_trajectory_length
));
}
// Reconstruct phase space
let trajectory = Trajectory::from_timeseries(
data,
self.embedding_dimension,
self.time_delay,
);
// Calculate Lyapunov exponent
let lyapunov = self.calculate_lyapunov_exponent(&trajectory);
// Calculate correlation dimension
let corr_dim = self.calculate_correlation_dimension(&trajectory);
// Detect attractor type
let attractor_type = self.classify_attractor(lyapunov, corr_dim);
// Calculate stability
let stability = self.calculate_stability(&trajectory);
Ok(AttractorInfo {
attractor_type,
lyapunov_exponent: lyapunov,
correlation_dimension: corr_dim,
is_chaotic: lyapunov > 0.0,
stability_index: stability,
})
}
/// Calculate largest Lyapunov exponent (indicator of chaos)
fn calculate_lyapunov_exponent(&self, trajectory: &Trajectory) -> f64 {
if trajectory.len() < 10 {
return 0.0;
}
let mut sum = 0.0;
let mut count = 0;
// Simplified Lyapunov calculation
for i in 0..trajectory.len().saturating_sub(1) {
let dist = self.euclidean_distance(
&trajectory.points[i].coordinates,
&trajectory.points[i + 1].coordinates,
);
if dist > 0.0 {
sum += dist.ln();
count += 1;
}
}
if count > 0 {
sum / count as f64
} else {
0.0
}
}
/// Calculate correlation dimension (Grassberger-Procaccia algorithm)
fn calculate_correlation_dimension(&self, trajectory: &Trajectory) -> f64 {
if trajectory.len() < 10 {
return 0.0;
}
let n = trajectory.len();
let sample_size = n.min(100); // Sample for efficiency
// Calculate distances between points
let mut distances = Vec::new();
for i in 0..sample_size {
for j in (i + 1)..sample_size {
let dist = self.euclidean_distance(
&trajectory.points[i].coordinates,
&trajectory.points[j].coordinates,
);
distances.push(dist);
}
}
if distances.is_empty() {
return 0.0;
}
// Estimate dimension from scaling
distances.sort_by(|a, b| a.partial_cmp(b).unwrap());
let median = distances[distances.len() / 2];
// Simplified correlation dimension estimate
let dim = if median > 0.0 {
(n as f64).ln() / median.ln()
} else {
0.0
};
dim.min(self.embedding_dimension as f64)
}
/// Classify attractor type based on characteristics
fn classify_attractor(&self, lyapunov: f64, corr_dim: f64) -> AttractorType {
if lyapunov > 0.1 {
// Positive Lyapunov => chaos
AttractorType::StrangeAttractor
} else if lyapunov < -0.1 {
// Negative Lyapunov => stable
if corr_dim < 0.5 {
AttractorType::FixedPoint
} else if corr_dim < 1.5 {
AttractorType::LimitCycle
} else {
AttractorType::Torus
}
} else {
// Near zero => borderline or transitional
AttractorType::Unknown
}
}
/// Calculate stability index (lower = more stable)
fn calculate_stability(&self, trajectory: &Trajectory) -> f64 {
if trajectory.len() < 2 {
return 1.0;
}
// Measure average deviation from trajectory center
let center = self.calculate_centroid(&trajectory.points);
let mut total_deviation = 0.0;
for point in &trajectory.points {
total_deviation += self.euclidean_distance(&point.coordinates, &center);
}
total_deviation / trajectory.len() as f64
}
/// Calculate centroid of point cloud
fn calculate_centroid(&self, points: &[PhasePoint]) -> Vec<f64> {
if points.is_empty() {
return vec![0.0; self.embedding_dimension];
}
let dim = points[0].coordinates.len();
let mut centroid = vec![0.0; dim];
for point in points {
for (i, &coord) in point.coordinates.iter().enumerate() {
centroid[i] += coord;
}
}
for coord in &mut centroid {
*coord /= points.len() as f64;
}
centroid
}
/// Calculate Euclidean distance between two points
fn euclidean_distance(&self, p1: &[f64], p2: &[f64]) -> f64 {
p1.iter()
.zip(p2.iter())
.map(|(a, b)| (a - b).powi(2))
.sum::<f64>()
.sqrt()
}
/// Predict next point in trajectory
pub fn predict_next(&self, trajectory: &Trajectory) -> Vec<f64> {
if trajectory.len() < 2 {
return vec![0.0; self.embedding_dimension];
}
// Simple linear extrapolation
let last = &trajectory.points[trajectory.len() - 1].coordinates;
let prev = &trajectory.points[trajectory.len() - 2].coordinates;
last.iter()
.zip(prev.iter())
.map(|(l, p)| 2.0 * l - p)
.collect()
}
}
impl Default for AttractorAnalyzer {
fn default() -> Self {
Self::new(3, 1)
}
}
/// Agent behavior analyzer using attractor theory
pub struct BehaviorAttractorAnalyzer {
analyzer: AttractorAnalyzer,
reward_history: VecDeque<f64>,
confidence_history: VecDeque<f64>,
max_history: usize,
}
impl BehaviorAttractorAnalyzer {
/// Create a new behavior analyzer
pub fn new(embedding_dim: usize, max_history: usize) -> Self {
Self {
analyzer: AttractorAnalyzer::new(embedding_dim, 1),
reward_history: VecDeque::new(),
confidence_history: VecDeque::new(),
max_history,
}
}
/// Update with new observation
pub fn observe(&mut self, reward: f64, confidence: f64) {
self.reward_history.push_back(reward);
self.confidence_history.push_back(confidence);
// Maintain max history
if self.reward_history.len() > self.max_history {
self.reward_history.pop_front();
}
if self.confidence_history.len() > self.max_history {
self.confidence_history.pop_front();
}
}
/// Analyze reward dynamics
pub fn analyze_reward_dynamics(&self) -> Result<AttractorInfo, String> {
let data: Vec<f64> = self.reward_history.iter().copied().collect();
self.analyzer.analyze(&data)
}
/// Analyze confidence dynamics
pub fn analyze_confidence_dynamics(&self) -> Result<AttractorInfo, String> {
let data: Vec<f64> = self.confidence_history.iter().copied().collect();
self.analyzer.analyze(&data)
}
/// Detect if agent is in stable regime
pub fn is_stable(&self) -> bool {
if let Ok(info) = self.analyze_reward_dynamics() {
info.attractor_type == AttractorType::FixedPoint
|| info.attractor_type == AttractorType::LimitCycle
} else {
false
}
}
/// Detect if agent behavior is chaotic
pub fn is_chaotic(&self) -> bool {
if let Ok(info) = self.analyze_reward_dynamics() {
info.is_chaotic
} else {
false
}
}
/// Get behavior summary
pub fn get_behavior_summary(&self) -> BehaviorSummary {
let reward_info = self.analyze_reward_dynamics().ok();
let confidence_info = self.analyze_confidence_dynamics().ok();
BehaviorSummary {
reward_attractor: reward_info,
confidence_attractor: confidence_info,
is_stable: self.is_stable(),
is_chaotic: self.is_chaotic(),
history_length: self.reward_history.len(),
}
}
}
/// Summary of agent behavior dynamics
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BehaviorSummary {
pub reward_attractor: Option<AttractorInfo>,
pub confidence_attractor: Option<AttractorInfo>,
pub is_stable: bool,
pub is_chaotic: bool,
pub history_length: usize,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_trajectory_embedding() {
let data = vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0];
let trajectory = Trajectory::from_timeseries(&data, 3, 1);
assert_eq!(trajectory.embedding_dim(), 3);
assert!(!trajectory.is_empty());
}
#[test]
fn test_fixed_point_detection() {
let analyzer = AttractorAnalyzer::new(2, 1);
// Constant values => fixed point
let data: Vec<f64> = (0..100).map(|_| 5.0).collect();
let info = analyzer.analyze(&data).unwrap();
assert_eq!(info.attractor_type, AttractorType::FixedPoint);
assert!(!info.is_chaotic);
}
#[test]
fn test_periodic_detection() {
let analyzer = AttractorAnalyzer::new(2, 1);
// Sine wave => limit cycle
let data: Vec<f64> = (0..100)
.map(|i| (i as f64 * 0.1).sin())
.collect();
let info = analyzer.analyze(&data).unwrap();
// Should detect some periodicity
assert_ne!(info.attractor_type, AttractorType::FixedPoint);
}
#[test]
fn test_chaotic_detection() {
let analyzer = AttractorAnalyzer::new(3, 1);
// Logistic map with chaotic parameter
let mut data = Vec::new();
let mut x = 0.1;
let r = 3.9; // Chaotic regime
for _ in 0..200 {
x = r * x * (1.0 - x);
data.push(x);
}
let info = analyzer.analyze(&data).unwrap();
// Logistic map at r=3.9 should be chaotic
println!("Lyapunov exponent: {}", info.lyapunov_exponent);
println!("Attractor type: {:?}", info.attractor_type);
}
#[test]
fn test_behavior_analyzer() {
let mut analyzer = BehaviorAttractorAnalyzer::new(2, 100);
// Simulate stable learning (converging rewards)
for i in 0..150 {
let reward = 0.5 + 0.5 * (-i as f64 / 20.0).exp();
let confidence = 0.7 + 0.2 * (i as f64 / 150.0);
analyzer.observe(reward, confidence);
}
let summary = analyzer.get_behavior_summary();
println!("Behavior summary: {:?}", summary);
// Should detect convergence
assert!(summary.is_stable || summary.history_length > 100);
}
#[test]
fn test_prediction() {
let analyzer = AttractorAnalyzer::new(2, 1);
let data = vec![1.0, 2.0, 3.0, 4.0, 5.0];
let trajectory = Trajectory::from_timeseries(&data, 2, 1);
let next = analyzer.predict_next(&trajectory);
assert_eq!(next.len(), 2);
println!("Predicted next point: {:?}", next);
}
}
+274
View File
@@ -0,0 +1,274 @@
//! Knowledge graph and theorem store for dynamic knowledge representation
use serde::{Deserialize, Serialize};
use std::collections::{HashMap, HashSet};
use async_trait::async_trait;
use super::reasoning::Theorem;
/// Knowledge graph for storing entities, relations, and theorems
pub struct KnowledgeGraph {
/// Entities in the knowledge graph
entities: HashMap<String, Entity>,
/// Relations between entities
relations: Vec<Relation>,
/// Theorems and verified knowledge
theorems: Vec<Theorem>,
/// Temporal knowledge (time-windowed facts)
temporal_facts: Vec<TemporalFact>,
/// Entity embeddings for semantic similarity
embeddings: HashMap<String, Vec<f64>>,
}
impl KnowledgeGraph {
pub fn new() -> Self {
Self {
entities: HashMap::new(),
relations: Vec::new(),
theorems: Vec::new(),
temporal_facts: Vec::new(),
embeddings: HashMap::new(),
}
}
/// Extract entities from text
pub async fn extract_entities(&self, text: &str) -> Result<Vec<Entity>, String> {
let mut entities = Vec::new();
// Simple entity extraction (can be enhanced with NER)
let words: Vec<&str> = text.split_whitespace().collect();
for (i, word) in words.iter().enumerate() {
// Capitalize words might be entities
if word.chars().next().map(|c| c.is_uppercase()).unwrap_or(false) {
entities.push(Entity {
id: format!("entity_{}", i),
name: word.to_string(),
entity_type: EntityType::Unknown,
attributes: HashMap::new(),
confidence: 0.7,
});
}
}
// Extract numeric values
for (i, word) in words.iter().enumerate() {
if word.parse::<f64>().is_ok() {
entities.push(Entity {
id: format!("value_{}", i),
name: word.to_string(),
entity_type: EntityType::Value,
attributes: HashMap::new(),
confidence: 0.9,
});
}
}
Ok(entities)
}
/// Update knowledge graph with new entities
pub async fn update(&mut self, entities: Vec<Entity>) -> Result<(), String> {
for entity in entities {
// Check if entity exists
if let Some(existing) = self.entities.get_mut(&entity.id) {
// Update existing entity
existing.confidence = (existing.confidence + entity.confidence) / 2.0;
for (key, value) in entity.attributes {
existing.attributes.insert(key, value);
}
} else {
// Add new entity
self.entities.insert(entity.id.clone(), entity);
}
}
Ok(())
}
/// Add a relation between entities
pub fn add_relation(&mut self, relation: Relation) {
self.relations.push(relation);
}
/// Add a verified theorem
pub fn add_theorem(&mut self, theorem: Theorem) {
self.theorems.push(theorem);
}
/// Query entities by type
pub fn query_entities(&self, entity_type: EntityType) -> Vec<&Entity> {
self.entities.values()
.filter(|e| e.entity_type == entity_type)
.collect()
}
/// Find related entities
pub fn find_related(&self, entity_id: &str, max_depth: usize) -> Vec<String> {
let mut related = HashSet::new();
let mut to_explore = vec![(entity_id.to_string(), 0)];
while let Some((current_id, depth)) = to_explore.pop() {
if depth >= max_depth {
continue;
}
// Find relations involving this entity
for relation in &self.relations {
if relation.subject == current_id {
related.insert(relation.object.clone());
to_explore.push((relation.object.clone(), depth + 1));
} else if relation.object == current_id {
related.insert(relation.subject.clone());
to_explore.push((relation.subject.clone(), depth + 1));
}
}
}
related.into_iter().collect()
}
/// Add temporal fact (fact with time window)
pub fn add_temporal_fact(&mut self, fact: TemporalFact) {
self.temporal_facts.push(fact);
// Clean up old facts
let now = chrono::Utc::now().timestamp();
self.temporal_facts.retain(|f| {
if let Some(end) = f.valid_until {
end > now
} else {
true // Keep facts without expiration
}
});
}
/// Get facts valid at a specific time
pub fn get_facts_at_time(&self, timestamp: i64) -> Vec<&TemporalFact> {
self.temporal_facts.iter()
.filter(|f| {
f.valid_from <= timestamp &&
f.valid_until.map(|t| timestamp <= t).unwrap_or(true)
})
.collect()
}
/// Compute semantic similarity between entities
pub fn compute_similarity(&self, entity1: &str, entity2: &str) -> f64 {
if let (Some(emb1), Some(emb2)) = (
self.embeddings.get(entity1),
self.embeddings.get(entity2)
) {
// Cosine similarity
let dot_product: f64 = emb1.iter()
.zip(emb2.iter())
.map(|(a, b)| a * b)
.sum();
let norm1: f64 = emb1.iter().map(|x| x * x).sum::<f64>().sqrt();
let norm2: f64 = emb2.iter().map(|x| x * x).sum::<f64>().sqrt();
if norm1 > 0.0 && norm2 > 0.0 {
dot_product / (norm1 * norm2)
} else {
0.0
}
} else {
0.0
}
}
/// Update entity embedding
pub fn update_embedding(&mut self, entity_id: String, embedding: Vec<f64>) {
self.embeddings.insert(entity_id, embedding);
}
pub fn entity_count(&self) -> usize {
self.entities.len()
}
pub fn theorem_count(&self) -> usize {
self.theorems.len()
}
pub fn relation_count(&self) -> usize {
self.relations.len()
}
}
/// An entity in the knowledge graph
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Entity {
pub id: String,
pub name: String,
pub entity_type: EntityType,
pub attributes: HashMap<String, String>,
pub confidence: f64,
}
/// Types of entities
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum EntityType {
Person,
Place,
Organization,
Concept,
Event,
Value,
Unknown,
}
/// A relation between two entities
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Relation {
pub id: String,
pub subject: String,
pub predicate: String,
pub object: String,
pub confidence: f64,
pub source: String,
}
/// A temporal fact (fact valid within a time window)
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TemporalFact {
pub fact: String,
pub valid_from: i64,
pub valid_until: Option<i64>,
pub confidence: f64,
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_knowledge_graph() {
let mut kg = KnowledgeGraph::new();
let entities = kg.extract_entities("Alice works at Google").await.unwrap();
kg.update(entities).await.unwrap();
assert!(kg.entity_count() > 0);
}
#[test]
fn test_temporal_facts() {
let mut kg = KnowledgeGraph::new();
let now = chrono::Utc::now().timestamp();
kg.add_temporal_fact(TemporalFact {
fact: "Weather is sunny".to_string(),
valid_from: now,
valid_until: Some(now + 3600),
confidence: 0.9,
});
let facts = kg.get_facts_at_time(now + 1800);
assert_eq!(facts.len(), 1);
}
}
+353
View File
@@ -0,0 +1,353 @@
//! Stream learning and online adaptation
use serde::{Deserialize, Serialize};
use std::collections::{HashMap, VecDeque};
use async_trait::async_trait;
use super::agent::Action;
/// Stream learner for online adaptation
pub struct StreamLearner {
/// Online model for learning
model: OnlineModel,
/// Learning rate
learning_rate: f64,
/// Experience buffer for replay
experience_buffer: VecDeque<Experience>,
/// Buffer size
buffer_size: usize,
/// Total iterations
iterations: u64,
/// Adaptation strategy
strategy: AdaptationStrategy,
}
impl StreamLearner {
pub fn new(learning_rate: f64) -> Self {
Self {
model: OnlineModel::new(),
learning_rate,
experience_buffer: VecDeque::new(),
buffer_size: 1000,
iterations: 0,
strategy: AdaptationStrategy::default(),
}
}
/// Update model with new experience
pub async fn update(
&mut self,
action: &Action,
reward: f64,
context: &str,
) -> Result<(), String> {
self.iterations += 1;
// Create experience
let experience = Experience {
action: action.clone(),
reward,
context: context.to_string(),
timestamp: chrono::Utc::now().timestamp(),
};
// Add to buffer
self.experience_buffer.push_back(experience.clone());
if self.experience_buffer.len() > self.buffer_size {
self.experience_buffer.pop_front();
}
// Update model based on strategy
match &self.strategy {
AdaptationStrategy::Immediate => {
self.model.update_immediate(&experience, self.learning_rate).await?;
}
AdaptationStrategy::Batched { batch_size } => {
if self.iterations % batch_size == 0 {
self.model.update_batch(&self.experience_buffer, self.learning_rate).await?;
}
}
AdaptationStrategy::ExperienceReplay { replay_size } => {
self.model.update_immediate(&experience, self.learning_rate).await?;
// Replay random experiences
let replay_samples = self.sample_experiences(*replay_size);
for sample in replay_samples {
self.model.update_immediate(&sample, self.learning_rate * 0.5).await?;
}
}
}
Ok(())
}
/// Sample random experiences for replay (using simple deterministic sampling)
fn sample_experiences(&self, n: usize) -> Vec<Experience> {
// Simple deterministic sampling: take evenly spaced samples
let experiences: Vec<_> = self.experience_buffer.iter().cloned().collect();
let total = experiences.len();
if total == 0 || n == 0 {
return Vec::new();
}
let step = (total as f64 / n as f64).max(1.0) as usize;
experiences.iter()
.step_by(step)
.take(n)
.cloned()
.collect()
}
/// Predict reward for an action
pub async fn predict_reward(&self, action: &Action, context: &str) -> f64 {
self.model.predict(action, context).await
}
/// Get learning statistics
pub fn get_stats(&self) -> LearningStats {
LearningStats {
iterations: self.iterations,
buffer_size: self.experience_buffer.len(),
average_reward: self.compute_average_reward(),
model_parameters: self.model.parameter_count(),
}
}
fn compute_average_reward(&self) -> f64 {
if self.experience_buffer.is_empty() {
return 0.0;
}
let sum: f64 = self.experience_buffer.iter()
.map(|e| e.reward)
.sum();
sum / self.experience_buffer.len() as f64
}
pub fn iteration_count(&self) -> u64 {
self.iterations
}
}
/// Online learning model
pub struct OnlineModel {
/// Feature weights
weights: HashMap<String, f64>,
/// Bias term
bias: f64,
/// Feature statistics for normalization
feature_stats: HashMap<String, FeatureStats>,
}
impl OnlineModel {
pub fn new() -> Self {
Self {
weights: HashMap::new(),
bias: 0.0,
feature_stats: HashMap::new(),
}
}
/// Extract features from action and context
fn extract_features(&self, action: &Action, context: &str) -> HashMap<String, f64> {
let mut features = HashMap::new();
// Action type feature
features.insert(
format!("action_{}", action.action_type),
1.0,
);
// Number of parameters
features.insert(
"param_count".to_string(),
action.parameters.len() as f64,
);
// Number of tool calls
features.insert(
"tool_count".to_string(),
action.tool_calls.len() as f64,
);
// Context length
features.insert(
"context_length".to_string(),
context.len() as f64 / 100.0, // Normalize
);
// Expected reward (from action)
features.insert(
"expected_reward".to_string(),
action.expected_reward,
);
features
}
/// Predict reward for given features
pub async fn predict(&self, action: &Action, context: &str) -> f64 {
let features = self.extract_features(action, context);
let mut prediction = self.bias;
for (feature, value) in features {
if let Some(weight) = self.weights.get(&feature) {
prediction += weight * value;
}
}
prediction
}
/// Update model immediately with single experience
pub async fn update_immediate(
&mut self,
experience: &Experience,
learning_rate: f64,
) -> Result<(), String> {
let features = self.extract_features(&experience.action, &experience.context);
let prediction = self.predict(&experience.action, &experience.context).await;
// Gradient descent update
let error = experience.reward - prediction;
// Update bias
self.bias += learning_rate * error;
// Update weights
for (feature, value) in features {
let weight = self.weights.entry(feature.clone()).or_insert(0.0);
*weight += learning_rate * error * value;
// Update feature statistics
let stats = self.feature_stats.entry(feature).or_insert(FeatureStats::default());
stats.update(value);
}
Ok(())
}
/// Update model with batch of experiences
pub async fn update_batch(
&mut self,
experiences: &VecDeque<Experience>,
learning_rate: f64,
) -> Result<(), String> {
for experience in experiences {
self.update_immediate(experience, learning_rate).await?;
}
Ok(())
}
pub fn parameter_count(&self) -> usize {
self.weights.len() + 1 // weights + bias
}
}
/// Experience tuple for learning
#[derive(Debug, Clone)]
pub struct Experience {
pub action: Action,
pub reward: f64,
pub context: String,
pub timestamp: i64,
}
/// Adaptation strategy for online learning
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum AdaptationStrategy {
/// Update immediately after each experience
Immediate,
/// Update in batches
Batched { batch_size: u64 },
/// Use experience replay
ExperienceReplay { replay_size: usize },
}
impl Default for AdaptationStrategy {
fn default() -> Self {
AdaptationStrategy::Immediate
}
}
/// Feature statistics for normalization
#[derive(Debug, Clone, Default)]
struct FeatureStats {
count: u64,
sum: f64,
sum_squared: f64,
}
impl FeatureStats {
fn update(&mut self, value: f64) {
self.count += 1;
self.sum += value;
self.sum_squared += value * value;
}
fn mean(&self) -> f64 {
if self.count == 0 {
0.0
} else {
self.sum / self.count as f64
}
}
fn variance(&self) -> f64 {
if self.count == 0 {
0.0
} else {
let mean = self.mean();
(self.sum_squared / self.count as f64) - (mean * mean)
}
}
}
/// Learning statistics
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LearningStats {
pub iterations: u64,
pub buffer_size: usize,
pub average_reward: f64,
pub model_parameters: usize,
}
#[cfg(test)]
mod tests {
use super::*;
use std::collections::HashMap;
#[tokio::test]
async fn test_stream_learner() {
let mut learner = StreamLearner::new(0.01);
let action = Action {
action_type: "test".to_string(),
description: "Test action".to_string(),
parameters: HashMap::new(),
tool_calls: vec![],
expected_outcome: None,
expected_reward: 0.5,
};
let result = learner.update(&action, 1.0, "test context").await;
assert!(result.is_ok());
let stats = learner.get_stats();
assert_eq!(stats.iterations, 1);
}
}
+263
View File
@@ -0,0 +1,263 @@
//! # Lean Agentic Learning System
//!
//! A revolutionary learning framework combining:
//! - Formal reasoning (Lean-style theorem proving)
//! - Agentic AI (autonomous decision-making)
//! - Stream learning (real-time online adaptation)
//! - Knowledge evolution (dynamic theorem store)
//!
//! ## Architecture
//!
//! ```text
//! ┌─────────────────────────────────────────────────────────┐
//! │ Lean Agentic Learning System │
//! ├─────────────────────────────────────────────────────────┤
//! │ │
//! │ ┌──────────────┐ ┌──────────────┐ │
//! │ │ Formal │ │ Agentic │ │
//! │ │ Reasoning │◄────►│ Loop │ │
//! │ │ Engine │ │ (P-A-O-L) │ │
//! │ └──────┬───────┘ └──────┬───────┘ │
//! │ │ │ │
//! │ │ ┌────────────────▼─────┐ │
//! │ └───►│ Knowledge Graph & │ │
//! │ │ Theorem Store │ │
//! │ └────────────┬─────────┘ │
//! │ │ │
//! │ ┌────────────▼─────────┐ │
//! │ │ Stream Learning & │ │
//! │ │ Online Adaptation │ │
//! │ └──────────────────────┘ │
//! └─────────────────────────────────────────────────────────┘
//! ```
pub mod reasoning;
pub mod agent;
pub mod knowledge;
pub mod learning;
pub mod types;
pub mod optimized;
pub mod temporal;
pub mod scheduler;
pub mod attractor;
pub mod temporal_neural;
pub mod strange_loop;
pub use reasoning::{FormalReasoner, Theorem, Proof, ProofStep};
pub use agent::{AgenticLoop, Action, Observation, Plan, LearningSignal};
pub use knowledge::{KnowledgeGraph, TheoremStore, Entity, Relation};
pub use learning::{StreamLearner, OnlineModel, AdaptationStrategy};
pub use types::{AgentState, Context, Reward};
pub use optimized::{
FeatureCache, BufferPool, PredictionCache, BatchProcessor,
FastEntityExtractor, fast_hash, simd,
};
pub use temporal::{
TemporalComparator, Sequence, ComparisonAlgorithm, CacheStats,
};
pub use scheduler::{
RealtimeScheduler, ScheduledTask, SchedulingPolicy, Priority,
SchedulableAction, SchedulerStats,
};
pub use attractor::{
AttractorAnalyzer, BehaviorAttractorAnalyzer, AttractorType,
AttractorInfo, Trajectory, PhasePoint, BehaviorSummary,
};
pub use temporal_neural::{
TemporalNeuralSolver, TemporalFormula, TemporalOperator,
TemporalTrace, TemporalState, VerificationResult,
};
pub use midstreamer_strange_loop::{
MetaLearner, MetaLevel, MetaKnowledge, StrangeLoop,
ModificationRule, SafetyConstraint, MetaLearningSummary,
};
use async_trait::async_trait;
use serde::{Deserialize, Serialize};
use std::sync::Arc;
use tokio::sync::RwLock;
/// The main lean agentic system orchestrator
pub struct LeanAgenticSystem {
/// Formal reasoning engine for verification
pub reasoner: Arc<RwLock<FormalReasoner>>,
/// Agentic loop for autonomous decision-making
pub agent_loop: Arc<RwLock<AgenticLoop>>,
/// Knowledge graph and theorem store
pub knowledge: Arc<RwLock<KnowledgeGraph>>,
/// Stream learning system
pub learner: Arc<RwLock<StreamLearner>>,
/// System configuration
pub config: LeanAgenticConfig,
}
/// Configuration for the lean agentic system
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LeanAgenticConfig {
/// Enable formal verification of actions
pub enable_formal_verification: bool,
/// Learning rate for online adaptation
pub learning_rate: f64,
/// Maximum planning depth
pub max_planning_depth: usize,
/// Confidence threshold for action execution
pub action_threshold: f64,
/// Enable multi-agent collaboration
pub enable_multi_agent: bool,
/// Knowledge graph update frequency
pub kg_update_freq: u64,
}
impl Default for LeanAgenticConfig {
fn default() -> Self {
Self {
enable_formal_verification: true,
learning_rate: 0.01,
max_planning_depth: 5,
action_threshold: 0.7,
enable_multi_agent: true,
kg_update_freq: 100,
}
}
}
impl LeanAgenticSystem {
/// Create a new lean agentic system
pub fn new(config: LeanAgenticConfig) -> Self {
Self {
reasoner: Arc::new(RwLock::new(FormalReasoner::new())),
agent_loop: Arc::new(RwLock::new(AgenticLoop::new(config.clone()))),
knowledge: Arc::new(RwLock::new(KnowledgeGraph::new())),
learner: Arc::new(RwLock::new(StreamLearner::new(config.learning_rate))),
config,
}
}
/// Process a stream chunk with lean agentic learning
pub async fn process_stream_chunk(
&self,
chunk: &str,
context: Context,
) -> Result<ProcessingResult, LeanAgenticError> {
// 1. Update knowledge graph with new information
let mut kg = self.knowledge.write().await;
let entities = kg.extract_entities(chunk).await?;
kg.update(entities).await?;
drop(kg);
// 2. Agent loop: Plan-Act-Observe-Learn
let mut agent = self.agent_loop.write().await;
let plan = agent.plan(&context, chunk).await?;
let action = agent.select_action(&plan).await?;
// 3. Formal verification (if enabled)
if self.config.enable_formal_verification {
let reasoner = self.reasoner.read().await;
let proof = reasoner.verify_action(&action, &context).await?;
if !proof.is_valid() {
return Err(LeanAgenticError::VerificationFailed(proof));
}
}
// 4. Execute action
let observation = agent.execute(&action).await?;
// 5. Online learning and adaptation
let mut learner = self.learner.write().await;
let reward = agent.compute_reward(&observation).await?;
learner.update(&action, reward, chunk).await?;
// 6. Learn from experience
agent.learn(LearningSignal {
action: action.clone(),
observation: observation.clone(),
reward,
}).await?;
Ok(ProcessingResult {
action,
observation,
reward,
verified: self.config.enable_formal_verification,
})
}
/// Get system statistics
pub async fn get_stats(&self) -> SystemStats {
let kg = self.knowledge.read().await;
let learner = self.learner.read().await;
let agent = self.agent_loop.read().await;
SystemStats {
total_theorems: kg.theorem_count(),
total_entities: kg.entity_count(),
learning_iterations: learner.iteration_count(),
total_actions: agent.action_count(),
average_reward: agent.average_reward(),
}
}
}
/// Result of processing a stream chunk
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProcessingResult {
pub action: Action,
pub observation: Observation,
pub reward: f64,
pub verified: bool,
}
/// System statistics
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SystemStats {
pub total_theorems: usize,
pub total_entities: usize,
pub learning_iterations: u64,
pub total_actions: u64,
pub average_reward: f64,
}
/// Errors that can occur in the lean agentic system
#[derive(Debug, thiserror::Error)]
pub enum LeanAgenticError {
#[error("Formal verification failed: {0:?}")]
VerificationFailed(Proof),
#[error("Planning error: {0}")]
PlanningError(String),
#[error("Action execution failed: {0}")]
ActionExecutionError(String),
#[error("Learning error: {0}")]
LearningError(String),
#[error("Knowledge graph error: {0}")]
KnowledgeGraphError(String),
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_lean_agentic_system() {
let config = LeanAgenticConfig::default();
let system = LeanAgenticSystem::new(config);
let context = Context::default();
let chunk = "Hello, world!";
let result = system.process_stream_chunk(chunk, context).await;
assert!(result.is_ok());
}
}
+369
View File
@@ -0,0 +1,369 @@
//! Optimized implementations for ultra-low latency processing
//!
//! These optimizations focus on:
//! - Reducing allocations
//! - Lock-free data structures where possible
//! - Pre-computed feature extractors
//! - Cached predictions
//! - Batch processing
use super::types::*;
use super::agent::Action;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::sync::Arc;
/// Optimized feature cache for fast lookup
pub struct FeatureCache {
cache: HashMap<u64, Vec<f64>>,
max_size: usize,
}
impl FeatureCache {
pub fn new(max_size: usize) -> Self {
Self {
cache: HashMap::with_capacity(max_size),
max_size,
}
}
pub fn get(&self, key: u64) -> Option<&Vec<f64>> {
self.cache.get(&key)
}
pub fn insert(&mut self, key: u64, features: Vec<f64>) {
if self.cache.len() >= self.max_size {
// Simple eviction: remove first entry (in practice, use LRU)
if let Some(first_key) = self.cache.keys().next().copied() {
self.cache.remove(&first_key);
}
}
self.cache.insert(key, features);
}
pub fn clear(&mut self) {
self.cache.clear();
}
}
/// Pre-allocated buffer pool for zero-allocation processing
pub struct BufferPool {
buffers: Vec<Vec<u8>>,
buffer_size: usize,
}
impl BufferPool {
pub fn new(pool_size: usize, buffer_size: usize) -> Self {
let mut buffers = Vec::with_capacity(pool_size);
for _ in 0..pool_size {
buffers.push(Vec::with_capacity(buffer_size));
}
Self {
buffers,
buffer_size,
}
}
pub fn acquire(&mut self) -> Vec<u8> {
self.buffers.pop().unwrap_or_else(|| Vec::with_capacity(self.buffer_size))
}
pub fn release(&mut self, mut buffer: Vec<u8>) {
buffer.clear();
if buffer.capacity() == self.buffer_size {
self.buffers.push(buffer);
}
}
}
/// Fast hash function for action fingerprinting
#[inline(always)]
pub fn fast_hash(action: &Action) -> u64 {
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
let mut hasher = DefaultHasher::new();
action.action_type.hash(&mut hasher);
action.parameters.len().hash(&mut hasher);
hasher.finish()
}
/// Optimized entity extraction with pre-allocated buffers
pub struct FastEntityExtractor {
buffer: String,
patterns: Vec<EntityPattern>,
}
#[derive(Clone)]
struct EntityPattern {
prefix: &'static str,
entity_type: EntityType,
}
impl FastEntityExtractor {
pub fn new() -> Self {
Self {
buffer: String::with_capacity(1024),
patterns: vec![
EntityPattern {
prefix: "weather",
entity_type: EntityType::Concept,
},
EntityPattern {
prefix: "schedule",
entity_type: EntityType::Event,
},
EntityPattern {
prefix: "calendar",
entity_type: EntityType::Concept,
},
],
}
}
pub fn extract(&mut self, text: &str) -> Vec<(String, EntityType)> {
let mut entities = Vec::new();
let text_lower = text.to_lowercase();
// Fast pattern matching
for pattern in &self.patterns {
if text_lower.contains(pattern.prefix) {
entities.push((pattern.prefix.to_string(), pattern.entity_type.clone()));
}
}
// Extract capitalized words (potential names)
for word in text.split_whitespace() {
if let Some(first_char) = word.chars().next() {
if first_char.is_uppercase() && word.len() > 1 {
entities.push((word.to_string(), EntityType::Unknown));
}
}
}
entities
}
}
/// Lock-free prediction cache for concurrent access
pub struct PredictionCache {
predictions: Arc<dashmap::DashMap<u64, f64>>,
max_size: usize,
}
impl PredictionCache {
pub fn new(max_size: usize) -> Self {
Self {
predictions: Arc::new(dashmap::DashMap::with_capacity(max_size)),
max_size,
}
}
pub fn get(&self, key: u64) -> Option<f64> {
self.predictions.get(&key).map(|v| *v)
}
pub fn insert(&self, key: u64, value: f64) {
if self.predictions.len() >= self.max_size {
// Simple eviction
if let Some(entry) = self.predictions.iter().next() {
let key_to_remove = *entry.key();
drop(entry);
self.predictions.remove(&key_to_remove);
}
}
self.predictions.insert(key, value);
}
pub fn len(&self) -> usize {
self.predictions.len()
}
}
/// Batch processor for amortizing costs
pub struct BatchProcessor<T> {
batch: Vec<T>,
batch_size: usize,
}
impl<T> BatchProcessor<T> {
pub fn new(batch_size: usize) -> Self {
Self {
batch: Vec::with_capacity(batch_size),
batch_size,
}
}
pub fn add(&mut self, item: T) -> Option<Vec<T>> {
self.batch.push(item);
if self.batch.len() >= self.batch_size {
Some(std::mem::replace(&mut self.batch, Vec::with_capacity(self.batch_size)))
} else {
None
}
}
pub fn flush(&mut self) -> Vec<T> {
std::mem::replace(&mut self.batch, Vec::with_capacity(self.batch_size))
}
pub fn len(&self) -> usize {
self.batch.len()
}
}
/// SIMD-optimized vector operations (when available)
#[cfg(target_arch = "x86_64")]
pub mod simd {
#[inline(always)]
pub fn dot_product(a: &[f64], b: &[f64]) -> f64 {
assert_eq!(a.len(), b.len());
let mut sum = 0.0;
let len = a.len();
let chunks = len / 4;
// Process 4 elements at a time
for i in 0..chunks {
let idx = i * 4;
sum += a[idx] * b[idx]
+ a[idx + 1] * b[idx + 1]
+ a[idx + 2] * b[idx + 2]
+ a[idx + 3] * b[idx + 3];
}
// Handle remainder
for i in (chunks * 4)..len {
sum += a[i] * b[i];
}
sum
}
#[inline(always)]
pub fn cosine_similarity(a: &[f64], b: &[f64]) -> f64 {
let dot = dot_product(a, b);
let norm_a = dot_product(a, a).sqrt();
let norm_b = dot_product(b, b).sqrt();
if norm_a > 0.0 && norm_b > 0.0 {
dot / (norm_a * norm_b)
} else {
0.0
}
}
}
#[cfg(not(target_arch = "x86_64"))]
pub mod simd {
#[inline(always)]
pub fn dot_product(a: &[f64], b: &[f64]) -> f64 {
a.iter().zip(b.iter()).map(|(x, y)| x * y).sum()
}
#[inline(always)]
pub fn cosine_similarity(a: &[f64], b: &[f64]) -> f64 {
let dot = dot_product(a, b);
let norm_a: f64 = a.iter().map(|x| x * x).sum::<f64>().sqrt();
let norm_b: f64 = b.iter().map(|x| x * x).sum::<f64>().sqrt();
if norm_a > 0.0 && norm_b > 0.0 {
dot / (norm_a * norm_b)
} else {
0.0
}
}
}
/// Zero-copy message parser
pub struct MessageParser<'a> {
data: &'a str,
position: usize,
}
impl<'a> MessageParser<'a> {
pub fn new(data: &'a str) -> Self {
Self { data, position: 0 }
}
pub fn next_word(&mut self) -> Option<&'a str> {
self.skip_whitespace();
if self.position >= self.data.len() {
return None;
}
let start = self.position;
while self.position < self.data.len() && !self.data.as_bytes()[self.position].is_ascii_whitespace() {
self.position += 1;
}
Some(&self.data[start..self.position])
}
fn skip_whitespace(&mut self) {
while self.position < self.data.len() && self.data.as_bytes()[self.position].is_ascii_whitespace() {
self.position += 1;
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_feature_cache() {
let mut cache = FeatureCache::new(2);
cache.insert(1, vec![1.0, 2.0, 3.0]);
cache.insert(2, vec![4.0, 5.0, 6.0]);
assert!(cache.get(1).is_some());
assert!(cache.get(2).is_some());
// Should evict oldest
cache.insert(3, vec![7.0, 8.0, 9.0]);
assert!(cache.get(3).is_some());
}
#[test]
fn test_buffer_pool() {
let mut pool = BufferPool::new(2, 1024);
let buf1 = pool.acquire();
let buf2 = pool.acquire();
assert_eq!(buf1.capacity(), 1024);
assert_eq!(buf2.capacity(), 1024);
pool.release(buf1);
pool.release(buf2);
let buf3 = pool.acquire();
assert_eq!(buf3.capacity(), 1024);
}
#[test]
fn test_simd_operations() {
let a = vec![1.0, 2.0, 3.0, 4.0];
let b = vec![2.0, 3.0, 4.0, 5.0];
let dot = simd::dot_product(&a, &b);
assert!((dot - 40.0).abs() < 1e-10);
let sim = simd::cosine_similarity(&a, &b);
assert!(sim > 0.9 && sim <= 1.0);
}
#[test]
fn test_message_parser() {
let mut parser = MessageParser::new("Hello world test");
assert_eq!(parser.next_word(), Some("Hello"));
assert_eq!(parser.next_word(), Some("world"));
assert_eq!(parser.next_word(), Some("test"));
assert_eq!(parser.next_word(), None);
}
}
+276
View File
@@ -0,0 +1,276 @@
//! Formal reasoning engine inspired by Lean theorem proving
use serde::{Deserialize, Serialize};
use std::collections::{HashMap, HashSet};
use async_trait::async_trait;
use super::types::Context;
use super::agent::Action;
/// Formal reasoning engine for verifying agent actions
pub struct FormalReasoner {
/// Axioms and established theorems
theorem_base: Vec<Theorem>,
/// Inference rules
rules: Vec<InferenceRule>,
/// Proof cache for performance
proof_cache: HashMap<String, Proof>,
}
impl FormalReasoner {
pub fn new() -> Self {
let mut reasoner = Self {
theorem_base: Vec::new(),
rules: Vec::new(),
proof_cache: HashMap::new(),
};
// Initialize with basic axioms
reasoner.add_axiom(Theorem {
id: "axiom_identity".to_string(),
statement: "For all x, x = x".to_string(),
proof: None,
confidence: 1.0,
tags: vec!["axiom".to_string(), "identity".to_string()],
});
reasoner.add_axiom(Theorem {
id: "axiom_safety".to_string(),
statement: "Actions must not cause harm".to_string(),
proof: None,
confidence: 1.0,
tags: vec!["axiom".to_string(), "safety".to_string()],
});
// Add basic inference rules
reasoner.add_rule(InferenceRule {
name: "modus_ponens".to_string(),
premises: vec!["P".to_string(), "P -> Q".to_string()],
conclusion: "Q".to_string(),
});
reasoner
}
/// Add an axiom to the theorem base
pub fn add_axiom(&mut self, theorem: Theorem) {
self.theorem_base.push(theorem);
}
/// Add an inference rule
pub fn add_rule(&mut self, rule: InferenceRule) {
self.rules.push(rule);
}
/// Verify an action is safe and correct
pub async fn verify_action(
&self,
action: &Action,
context: &Context,
) -> Result<Proof, String> {
let proof_key = format!("{:?}_{}", action, context.session_id);
// Check cache first
if let Some(cached_proof) = self.proof_cache.get(&proof_key) {
return Ok(cached_proof.clone());
}
// Construct proof
let mut proof = Proof {
steps: Vec::new(),
valid: true,
confidence: 1.0,
};
// Step 1: Verify safety constraints
proof.steps.push(ProofStep {
rule: "safety_check".to_string(),
premises: vec![action.description.clone()],
conclusion: "Action is safe".to_string(),
confidence: self.verify_safety(action).await,
});
// Step 2: Verify preconditions
proof.steps.push(ProofStep {
rule: "precondition_check".to_string(),
premises: vec![format!("Context: {:?}", context)],
conclusion: "Preconditions satisfied".to_string(),
confidence: self.verify_preconditions(action, context).await,
});
// Step 3: Verify expected outcomes
proof.steps.push(ProofStep {
rule: "outcome_verification".to_string(),
premises: vec![format!("Expected: {:?}", action.expected_outcome)],
conclusion: "Outcomes are valid".to_string(),
confidence: self.verify_outcomes(action).await,
});
// Compute overall validity
proof.confidence = proof.steps.iter()
.map(|s| s.confidence)
.product::<f64>();
proof.valid = proof.confidence > 0.5;
Ok(proof)
}
async fn verify_safety(&self, action: &Action) -> f64 {
// Check against safety axioms
let safety_axiom = self.theorem_base.iter()
.find(|t| t.tags.contains(&"safety".to_string()));
if let Some(_axiom) = safety_axiom {
// Simple heuristic: actions with tool calls need verification
if action.tool_calls.is_empty() {
0.95 // High confidence for non-tool actions
} else {
0.8 // Moderate confidence for tool actions
}
} else {
0.7 // Default moderate confidence
}
}
async fn verify_preconditions(&self, action: &Action, context: &Context) -> f64 {
// Verify context has necessary information
if context.history.is_empty() {
return 0.5; // Low confidence with no history
}
// Check if action parameters are valid
let param_confidence = if action.parameters.is_empty() {
0.9
} else {
// Verify parameters make sense
0.85
};
param_confidence
}
async fn verify_outcomes(&self, action: &Action) -> f64 {
// Verify expected outcomes are reasonable
if let Some(ref outcome) = action.expected_outcome {
if !outcome.is_empty() {
0.9
} else {
0.7
}
} else {
0.6
}
}
/// Prove a new theorem from existing ones
pub async fn prove_theorem(
&mut self,
statement: String,
premises: Vec<String>,
) -> Result<Theorem, String> {
let mut proof = Proof {
steps: Vec::new(),
valid: false,
confidence: 0.0,
};
// Try to construct proof using available rules
for rule in &self.rules {
if self.can_apply_rule(rule, &premises) {
proof.steps.push(ProofStep {
rule: rule.name.clone(),
premises: premises.clone(),
conclusion: statement.clone(),
confidence: 0.9,
});
proof.valid = true;
proof.confidence = 0.9;
break;
}
}
if proof.valid {
let theorem = Theorem {
id: format!("theorem_{}", self.theorem_base.len()),
statement,
proof: Some(proof),
confidence: 0.9,
tags: vec!["derived".to_string()],
};
self.theorem_base.push(theorem.clone());
Ok(theorem)
} else {
Err("Could not construct proof".to_string())
}
}
fn can_apply_rule(&self, rule: &InferenceRule, premises: &[String]) -> bool {
// Simple pattern matching for now
premises.len() >= rule.premises.len()
}
pub fn theorem_count(&self) -> usize {
self.theorem_base.len()
}
}
/// A mathematical theorem or logical statement
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Theorem {
pub id: String,
pub statement: String,
pub proof: Option<Proof>,
pub confidence: f64,
pub tags: Vec<String>,
}
/// A formal proof
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Proof {
pub steps: Vec<ProofStep>,
pub valid: bool,
pub confidence: f64,
}
impl Proof {
pub fn is_valid(&self) -> bool {
self.valid && self.confidence > 0.5
}
}
/// A step in a proof
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProofStep {
pub rule: String,
pub premises: Vec<String>,
pub conclusion: String,
pub confidence: f64,
}
/// An inference rule for logical deduction
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct InferenceRule {
pub name: String,
pub premises: Vec<String>,
pub conclusion: String,
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_formal_reasoner() {
let mut reasoner = FormalReasoner::new();
let theorem = reasoner.prove_theorem(
"Q".to_string(),
vec!["P".to_string(), "P -> Q".to_string()],
).await;
assert!(theorem.is_ok());
}
}
+466
View File
@@ -0,0 +1,466 @@
//! Real-time scheduling for agent actions with nanosecond precision
//!
//! Integrates nanosecond-scheduler for:
//! - Priority-based task scheduling
//! - Deadline-aware execution
//! - Real-time guarantees
//! - Resource allocation
use serde::{Deserialize, Serialize};
use std::cmp::Ordering;
use std::collections::BinaryHeap;
use std::sync::Arc;
use std::time::{Duration, Instant};
use tokio::sync::RwLock;
use crate::lean_agentic::Action;
/// Scheduling policy
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum SchedulingPolicy {
/// First-In-First-Out
FIFO,
/// Rate-Monotonic (shorter periods have higher priority)
RateMonotonic,
/// Earliest Deadline First
EarliestDeadlineFirst,
/// Fixed Priority
FixedPriority,
}
/// Priority level for tasks
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
pub enum Priority {
Critical = 0,
High = 1,
Medium = 2,
Low = 3,
Background = 4,
}
/// A scheduled task
#[derive(Debug, Clone)]
pub struct ScheduledTask {
/// The action to execute
pub action: Action,
/// Priority level
pub priority: Priority,
/// Deadline (absolute time)
pub deadline: Instant,
/// Estimated execution time
pub estimated_duration: Duration,
/// Task ID
pub id: u64,
/// Arrival time
pub arrival_time: Instant,
}
impl PartialEq for ScheduledTask {
fn eq(&self, other: &Self) -> bool {
self.id == other.id
}
}
impl Eq for ScheduledTask {}
impl PartialOrd for ScheduledTask {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for ScheduledTask {
fn cmp(&self, other: &Self) -> Ordering {
// Reverse ordering for min-heap behavior (earliest deadline first)
other.deadline.cmp(&self.deadline)
.then_with(|| self.priority.cmp(&other.priority))
.then_with(|| self.id.cmp(&other.id))
}
}
/// Real-time scheduler for agent actions
pub struct RealtimeScheduler {
/// Scheduling policy
policy: SchedulingPolicy,
/// Task queue
queue: Arc<RwLock<BinaryHeap<ScheduledTask>>>,
/// Next task ID
next_id: Arc<RwLock<u64>>,
/// Scheduler statistics
stats: Arc<RwLock<SchedulerStats>>,
}
/// Scheduler statistics
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct SchedulerStats {
pub total_scheduled: u64,
pub total_executed: u64,
pub total_missed_deadlines: u64,
pub average_latency_ns: u64,
pub max_latency_ns: u64,
pub min_latency_ns: u64,
}
impl RealtimeScheduler {
/// Create a new real-time scheduler
pub fn new(policy: SchedulingPolicy) -> Self {
Self {
policy,
queue: Arc::new(RwLock::new(BinaryHeap::new())),
next_id: Arc::new(RwLock::new(0)),
stats: Arc::new(RwLock::new(SchedulerStats {
min_latency_ns: u64::MAX,
..Default::default()
})),
}
}
/// Schedule a task
pub async fn schedule(
&self,
action: Action,
priority: Priority,
deadline: Duration,
estimated_duration: Duration,
) -> u64 {
let mut id_lock = self.next_id.write().await;
let id = *id_lock;
*id_lock += 1;
drop(id_lock);
let now = Instant::now();
let task = ScheduledTask {
action,
priority,
deadline: now + deadline,
estimated_duration,
id,
arrival_time: now,
};
let mut queue = self.queue.write().await;
queue.push(task);
drop(queue);
let mut stats = self.stats.write().await;
stats.total_scheduled += 1;
drop(stats);
id
}
/// Get next task to execute
pub async fn next_task(&self) -> Option<ScheduledTask> {
let mut queue = self.queue.write().await;
match self.policy {
SchedulingPolicy::FIFO => {
// Convert to Vec, pop first, convert back
let mut tasks: Vec<_> = queue.drain().collect();
if tasks.is_empty() {
return None;
}
tasks.sort_by_key(|t| t.arrival_time);
let task = tasks.remove(0);
for t in tasks {
queue.push(t);
}
Some(task)
}
SchedulingPolicy::EarliestDeadlineFirst => {
// BinaryHeap is already sorted by deadline
queue.pop()
}
SchedulingPolicy::RateMonotonic | SchedulingPolicy::FixedPriority => {
// Convert to Vec, sort by priority, take highest
let mut tasks: Vec<_> = queue.drain().collect();
if tasks.is_empty() {
return None;
}
tasks.sort_by_key(|t| t.priority);
let task = tasks.remove(0);
for t in tasks {
queue.push(t);
}
Some(task)
}
}
}
/// Mark task as executed
pub async fn mark_executed(&self, task_id: u64, execution_time: Duration) {
let mut stats = self.stats.write().await;
stats.total_executed += 1;
let latency_ns = execution_time.as_nanos() as u64;
stats.average_latency_ns =
(stats.average_latency_ns * (stats.total_executed - 1) + latency_ns)
/ stats.total_executed;
stats.max_latency_ns = stats.max_latency_ns.max(latency_ns);
stats.min_latency_ns = stats.min_latency_ns.min(latency_ns);
}
/// Mark deadline as missed
pub async fn mark_deadline_missed(&self, _task_id: u64) {
let mut stats = self.stats.write().await;
stats.total_missed_deadlines += 1;
}
/// Get scheduler statistics
pub async fn get_stats(&self) -> SchedulerStats {
self.stats.read().await.clone()
}
/// Get queue length
pub async fn queue_len(&self) -> usize {
self.queue.read().await.len()
}
/// Clear all pending tasks
pub async fn clear(&self) {
let mut queue = self.queue.write().await;
queue.clear();
}
/// Check if a task would meet its deadline
pub async fn can_meet_deadline(&self, estimated_duration: Duration, deadline: Duration) -> bool {
let queue = self.queue.read().await;
let total_pending: Duration = queue.iter()
.map(|t| t.estimated_duration)
.sum();
total_pending + estimated_duration <= deadline
}
/// Get pending tasks count by priority
pub async fn tasks_by_priority(&self) -> Vec<(Priority, usize)> {
let queue = self.queue.read().await;
let mut counts = vec![
(Priority::Critical, 0),
(Priority::High, 0),
(Priority::Medium, 0),
(Priority::Low, 0),
(Priority::Background, 0),
];
for task in queue.iter() {
for (priority, count) in counts.iter_mut() {
if task.priority == *priority {
*count += 1;
break;
}
}
}
counts
}
}
impl Default for RealtimeScheduler {
fn default() -> Self {
Self::new(SchedulingPolicy::EarliestDeadlineFirst)
}
}
/// Extension trait for Action with scheduling metadata
pub trait SchedulableAction {
/// Get estimated execution time
fn estimated_duration(&self) -> Duration;
/// Get priority
fn priority(&self) -> Priority;
/// Get deadline
fn deadline(&self) -> Duration;
}
impl SchedulableAction for Action {
fn estimated_duration(&self) -> Duration {
// Default estimate - can be overridden based on action type
Duration::from_millis(10)
}
fn priority(&self) -> Priority {
// Default priority - can be overridden based on action type
match self.confidence {
c if c > 0.9 => Priority::Critical,
c if c > 0.7 => Priority::High,
c if c > 0.5 => Priority::Medium,
c if c > 0.3 => Priority::Low,
_ => Priority::Background,
}
}
fn deadline(&self) -> Duration {
// Default deadline - can be overridden based on action type
Duration::from_millis(100)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::lean_agentic::types::Context;
fn create_test_action(name: &str, confidence: f64) -> Action {
Action {
name: name.to_string(),
parameters: serde_json::json!({}),
reasoning: format!("Test action: {}", name),
confidence,
context: Context::default(),
}
}
#[tokio::test]
async fn test_schedule_task() {
let scheduler = RealtimeScheduler::new(SchedulingPolicy::EarliestDeadlineFirst);
let action = create_test_action("test", 0.8);
let task_id = scheduler.schedule(
action,
Priority::High,
Duration::from_secs(1),
Duration::from_millis(10),
).await;
assert_eq!(task_id, 0);
assert_eq!(scheduler.queue_len().await, 1);
}
#[tokio::test]
async fn test_next_task_edf() {
let scheduler = RealtimeScheduler::new(SchedulingPolicy::EarliestDeadlineFirst);
// Schedule tasks with different deadlines
let action1 = create_test_action("task1", 0.8);
let action2 = create_test_action("task2", 0.8);
scheduler.schedule(
action1,
Priority::Medium,
Duration::from_secs(2),
Duration::from_millis(10),
).await;
scheduler.schedule(
action2,
Priority::Medium,
Duration::from_secs(1), // Shorter deadline
Duration::from_millis(10),
).await;
let next = scheduler.next_task().await.unwrap();
assert_eq!(next.action.name, "task2"); // Should get task with earlier deadline
}
#[tokio::test]
async fn test_priority_scheduling() {
let scheduler = RealtimeScheduler::new(SchedulingPolicy::FixedPriority);
let action1 = create_test_action("low", 0.4);
let action2 = create_test_action("high", 0.9);
scheduler.schedule(
action1,
Priority::Low,
Duration::from_secs(1),
Duration::from_millis(10),
).await;
scheduler.schedule(
action2,
Priority::Critical,
Duration::from_secs(1),
Duration::from_millis(10),
).await;
let next = scheduler.next_task().await.unwrap();
assert_eq!(next.action.name, "high"); // Should get high priority task
}
#[tokio::test]
async fn test_stats() {
let scheduler = RealtimeScheduler::new(SchedulingPolicy::EarliestDeadlineFirst);
let action = create_test_action("test", 0.8);
let task_id = scheduler.schedule(
action,
Priority::Medium,
Duration::from_secs(1),
Duration::from_millis(10),
).await;
scheduler.mark_executed(task_id, Duration::from_micros(500)).await;
let stats = scheduler.get_stats().await;
assert_eq!(stats.total_scheduled, 1);
assert_eq!(stats.total_executed, 1);
assert!(stats.average_latency_ns > 0);
}
#[tokio::test]
async fn test_can_meet_deadline() {
let scheduler = RealtimeScheduler::new(SchedulingPolicy::EarliestDeadlineFirst);
let can_meet = scheduler.can_meet_deadline(
Duration::from_millis(10),
Duration::from_secs(1),
).await;
assert!(can_meet);
// Add many tasks
for i in 0..100 {
let action = create_test_action(&format!("task{}", i), 0.8);
scheduler.schedule(
action,
Priority::Medium,
Duration::from_secs(10),
Duration::from_millis(100),
).await;
}
let can_meet = scheduler.can_meet_deadline(
Duration::from_millis(10),
Duration::from_millis(1),
).await;
assert!(!can_meet); // Should not be able to meet tight deadline
}
#[tokio::test]
async fn test_tasks_by_priority() {
let scheduler = RealtimeScheduler::new(SchedulingPolicy::FixedPriority);
for i in 0..5 {
let action = create_test_action(&format!("task{}", i), 0.8);
let priority = match i {
0 => Priority::Critical,
1 => Priority::High,
2 => Priority::Medium,
3 => Priority::Low,
4 => Priority::Background,
_ => Priority::Medium,
};
scheduler.schedule(
action,
priority,
Duration::from_secs(1),
Duration::from_millis(10),
).await;
}
let counts = scheduler.tasks_by_priority().await;
assert_eq!(counts.len(), 5);
for (priority, count) in counts {
if priority == Priority::Critical || priority == Priority::High ||
priority == Priority::Medium || priority == Priority::Low ||
priority == Priority::Background {
assert_eq!(count, 1);
}
}
}
}
+546
View File
@@ -0,0 +1,546 @@
//! Strange loops and meta-learning
//!
//! Integrates strange-loop for:
//! - Self-referential reasoning
//! - Meta-learning (learning to learn)
//! - Tangled hierarchies
//! - Safe self-modification
use serde::{Deserialize, Serialize};
use std::collections::{HashMap, VecDeque};
use std::sync::Arc;
/// Level in the meta-hierarchy
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum MetaLevel {
/// Object level (base learning)
Object = 0,
/// Meta level 1 (learning about learning)
Meta1 = 1,
/// Meta level 2 (learning about learning about learning)
Meta2 = 2,
/// Meta level 3 (highest practical level)
Meta3 = 3,
}
impl MetaLevel {
/// Get the next higher meta level
pub fn up(&self) -> Option<MetaLevel> {
match self {
MetaLevel::Object => Some(MetaLevel::Meta1),
MetaLevel::Meta1 => Some(MetaLevel::Meta2),
MetaLevel::Meta2 => Some(MetaLevel::Meta3),
MetaLevel::Meta3 => None, // Cap at Meta3
}
}
/// Get the next lower meta level
pub fn down(&self) -> Option<MetaLevel> {
match self {
MetaLevel::Object => None,
MetaLevel::Meta1 => Some(MetaLevel::Object),
MetaLevel::Meta2 => Some(MetaLevel::Meta1),
MetaLevel::Meta3 => Some(MetaLevel::Meta2),
}
}
/// Get level as integer
pub fn as_int(&self) -> usize {
*self as usize
}
}
/// Meta-knowledge about learning
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MetaKnowledge {
/// Level of abstraction
pub level: MetaLevel,
/// What was learned
pub content: String,
/// How effective was this learning
pub effectiveness: f64,
/// Conditions under which this applies
pub context: HashMap<String, String>,
/// Timestamp
pub timestamp: i64,
}
/// A strange loop - a self-referential pattern
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StrangeLoop {
/// Unique identifier
pub id: String,
/// Levels involved in the loop
pub levels: Vec<MetaLevel>,
/// Description of the loop
pub description: String,
/// Strength of the loop (how often it occurs)
pub strength: f64,
/// Whether this loop is beneficial or problematic
pub is_beneficial: bool,
}
/// Meta-learner that can learn about its own learning process
pub struct MetaLearner {
/// Current meta level of operation
current_level: MetaLevel,
/// Meta-knowledge store (hierarchical)
knowledge: HashMap<MetaLevel, Vec<MetaKnowledge>>,
/// Detected strange loops
strange_loops: Vec<StrangeLoop>,
/// Learning history for detecting patterns
learning_history: VecDeque<LearningEvent>,
/// Maximum history to keep
max_history: usize,
/// Self-modification rules
modification_rules: Vec<ModificationRule>,
/// Safety constraints
safety_constraints: Vec<SafetyConstraint>,
}
/// An event in the learning history
#[derive(Debug, Clone)]
struct LearningEvent {
level: MetaLevel,
content: String,
reward: f64,
timestamp: i64,
}
/// Rule for self-modification
#[derive(Debug, Clone)]
pub struct ModificationRule {
/// Condition that must be met
pub condition: String,
/// Action to take
pub action: String,
/// Priority (higher = more important)
pub priority: i32,
/// Whether this rule is enabled
pub enabled: bool,
}
/// Safety constraint for self-modification
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SafetyConstraint {
/// Name of the constraint
pub name: String,
/// Description
pub description: String,
/// Whether this constraint is violated
pub is_violated: bool,
}
impl MetaLearner {
/// Create a new meta-learner
pub fn new(max_history: usize) -> Self {
let mut knowledge = HashMap::new();
knowledge.insert(MetaLevel::Object, Vec::new());
knowledge.insert(MetaLevel::Meta1, Vec::new());
knowledge.insert(MetaLevel::Meta2, Vec::new());
knowledge.insert(MetaLevel::Meta3, Vec::new());
Self {
current_level: MetaLevel::Object,
knowledge,
strange_loops: Vec::new(),
learning_history: VecDeque::new(),
max_history,
modification_rules: Vec::new(),
safety_constraints: Self::default_safety_constraints(),
}
}
/// Get default safety constraints
fn default_safety_constraints() -> Vec<SafetyConstraint> {
vec![
SafetyConstraint {
name: "no_infinite_loops".to_string(),
description: "Prevent infinite self-reference".to_string(),
is_violated: false,
},
SafetyConstraint {
name: "preserve_core_functionality".to_string(),
description: "Don't modify core learning mechanisms".to_string(),
is_violated: false,
},
SafetyConstraint {
name: "bounded_meta_levels".to_string(),
description: "Don't exceed meta level 3".to_string(),
is_violated: false,
},
]
}
/// Learn at the current meta level
pub fn learn(&mut self, content: String, reward: f64) {
let meta_knowledge = MetaKnowledge {
level: self.current_level,
content: content.clone(),
effectiveness: reward,
context: HashMap::new(),
timestamp: chrono::Utc::now().timestamp(),
};
// Store at current level
if let Some(knowledge_vec) = self.knowledge.get_mut(&self.current_level) {
knowledge_vec.push(meta_knowledge);
}
// Add to history
self.learning_history.push_back(LearningEvent {
level: self.current_level,
content,
reward,
timestamp: chrono::Utc::now().timestamp(),
});
// Maintain max history
if self.learning_history.len() > self.max_history {
self.learning_history.pop_front();
}
// Detect meta-patterns (learn about learning)
self.detect_meta_patterns();
// Check for strange loops
self.detect_strange_loops();
}
/// Detect patterns in learning (meta-learning)
fn detect_meta_patterns(&mut self) {
if self.learning_history.len() < 10 {
return;
}
// Analyze recent learning events
let recent: Vec<_> = self.learning_history.iter().rev().take(10).collect();
// Calculate average reward at current level
let avg_reward: f64 = recent.iter().map(|e| e.reward).sum::<f64>() / recent.len() as f64;
// If learning is effective, record meta-knowledge
if avg_reward > 0.7 {
let meta_content = format!(
"Learning approach at {:?} level is effective (avg reward: {:.2})",
self.current_level, avg_reward
);
// Store at next meta level if possible
if let Some(next_level) = self.current_level.up() {
let meta_meta_knowledge = MetaKnowledge {
level: next_level,
content: meta_content,
effectiveness: avg_reward,
context: HashMap::new(),
timestamp: chrono::Utc::now().timestamp(),
};
if let Some(knowledge_vec) = self.knowledge.get_mut(&next_level) {
knowledge_vec.push(meta_meta_knowledge);
}
}
}
}
/// Detect strange loops (self-referential patterns)
fn detect_strange_loops(&mut self) {
if self.learning_history.len() < 5 {
return;
}
// Look for patterns where we learn about our own learning
let mut level_sequence: Vec<MetaLevel> = self
.learning_history
.iter()
.rev()
.take(5)
.map(|e| e.level)
.collect();
// Check for level transitions that form a loop
// e.g., Object -> Meta1 -> Meta2 -> Meta1 (loop between Meta1 and Meta2)
for i in 0..level_sequence.len().saturating_sub(2) {
if level_sequence[i] == level_sequence[i + 2] {
// Found a potential loop
let loop_id = format!("loop_{}_{}", i, chrono::Utc::now().timestamp());
let strange_loop = StrangeLoop {
id: loop_id,
levels: vec![level_sequence[i], level_sequence[i + 1]],
description: format!(
"Oscillation between {:?} and {:?}",
level_sequence[i], level_sequence[i + 1]
),
strength: 0.5,
is_beneficial: true, // Assume beneficial unless proven otherwise
};
// Check if loop already exists
if !self.strange_loops.iter().any(|l| l.levels == strange_loop.levels) {
self.strange_loops.push(strange_loop);
}
}
}
}
/// Ascend to a higher meta level
pub fn ascend(&mut self) -> Result<MetaLevel, String> {
if let Some(next_level) = self.current_level.up() {
self.current_level = next_level;
Ok(next_level)
} else {
Err("Already at highest meta level".to_string())
}
}
/// Descend to a lower meta level
pub fn descend(&mut self) -> Result<MetaLevel, String> {
if let Some(prev_level) = self.current_level.down() {
self.current_level = prev_level;
Ok(prev_level)
} else {
Err("Already at lowest meta level".to_string())
}
}
/// Get current meta level
pub fn current_level(&self) -> MetaLevel {
self.current_level
}
/// Get meta-knowledge at a specific level
pub fn get_knowledge_at_level(&self, level: MetaLevel) -> Vec<MetaKnowledge> {
self.knowledge.get(&level).cloned().unwrap_or_default()
}
/// Get all detected strange loops
pub fn get_strange_loops(&self) -> &[StrangeLoop] {
&self.strange_loops
}
/// Apply self-modification (with safety checks)
pub fn self_modify(&mut self, rule: ModificationRule) -> Result<(), String> {
// Check safety constraints
for constraint in &mut self.safety_constraints {
if rule.action.contains("infinite")
&& constraint.name == "no_infinite_loops"
{
constraint.is_violated = true;
return Err(format!("Safety constraint violated: {}", constraint.name));
}
if rule.action.contains("core")
&& constraint.name == "preserve_core_functionality"
{
constraint.is_violated = true;
return Err(format!("Safety constraint violated: {}", constraint.name));
}
}
// Add the modification rule
self.modification_rules.push(rule);
Ok(())
}
/// Check if any safety constraints are violated
pub fn safety_check(&self) -> Result<(), Vec<String>> {
let violations: Vec<String> = self
.safety_constraints
.iter()
.filter(|c| c.is_violated)
.map(|c| c.name.clone())
.collect();
if violations.is_empty() {
Ok(())
} else {
Err(violations)
}
}
/// Get summary of meta-learning state
pub fn get_summary(&self) -> MetaLearningSummary {
MetaLearningSummary {
current_level: self.current_level,
knowledge_counts: [
self.knowledge
.get(&MetaLevel::Object)
.map(|v| v.len())
.unwrap_or(0),
self.knowledge
.get(&MetaLevel::Meta1)
.map(|v| v.len())
.unwrap_or(0),
self.knowledge
.get(&MetaLevel::Meta2)
.map(|v| v.len())
.unwrap_or(0),
self.knowledge
.get(&MetaLevel::Meta3)
.map(|v| v.len())
.unwrap_or(0),
],
num_strange_loops: self.strange_loops.len(),
num_modification_rules: self.modification_rules.len(),
safety_violations: self
.safety_constraints
.iter()
.filter(|c| c.is_violated)
.count(),
}
}
}
/// Summary of meta-learning state
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MetaLearningSummary {
pub current_level: MetaLevel,
pub knowledge_counts: [usize; 4], // Object, Meta1, Meta2, Meta3
pub num_strange_loops: usize,
pub num_modification_rules: usize,
pub safety_violations: usize,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_meta_levels() {
let object = MetaLevel::Object;
assert_eq!(object.up(), Some(MetaLevel::Meta1));
assert_eq!(object.down(), None);
let meta3 = MetaLevel::Meta3;
assert_eq!(meta3.up(), None);
assert_eq!(meta3.down(), Some(MetaLevel::Meta2));
}
#[test]
fn test_meta_learner_creation() {
let learner = MetaLearner::new(100);
assert_eq!(learner.current_level(), MetaLevel::Object);
assert_eq!(learner.get_strange_loops().len(), 0);
}
#[test]
fn test_basic_learning() {
let mut learner = MetaLearner::new(100);
learner.learn("Test learning content".to_string(), 0.8);
let knowledge = learner.get_knowledge_at_level(MetaLevel::Object);
assert_eq!(knowledge.len(), 1);
assert_eq!(knowledge[0].content, "Test learning content");
assert_eq!(knowledge[0].effectiveness, 0.8);
}
#[test]
fn test_level_transitions() {
let mut learner = MetaLearner::new(100);
assert_eq!(learner.current_level(), MetaLevel::Object);
learner.ascend().unwrap();
assert_eq!(learner.current_level(), MetaLevel::Meta1);
learner.ascend().unwrap();
assert_eq!(learner.current_level(), MetaLevel::Meta2);
learner.descend().unwrap();
assert_eq!(learner.current_level(), MetaLevel::Meta1);
}
#[test]
fn test_meta_pattern_detection() {
let mut learner = MetaLearner::new(100);
// Learn many things with good rewards at object level
for i in 0..15 {
learner.learn(format!("Learning {}", i), 0.85);
}
// Should have detected meta-patterns and stored at Meta1 level
let meta1_knowledge = learner.get_knowledge_at_level(MetaLevel::Meta1);
println!("Meta1 knowledge: {:?}", meta1_knowledge);
// May or may not have meta-knowledge depending on timing
// Just verify it doesn't crash
assert!(meta1_knowledge.len() >= 0);
}
#[test]
fn test_strange_loop_detection() {
let mut learner = MetaLearner::new(100);
// Create a pattern that oscillates between levels
learner.learn("Object level".to_string(), 0.7);
learner.ascend().unwrap();
learner.learn("Meta1 level".to_string(), 0.7);
learner.descend().unwrap();
learner.learn("Object level again".to_string(), 0.7);
learner.ascend().unwrap();
learner.learn("Meta1 level again".to_string(), 0.7);
let loops = learner.get_strange_loops();
println!("Detected loops: {:?}", loops);
// May detect loops
assert!(loops.len() >= 0);
}
#[test]
fn test_safety_constraints() {
let mut learner = MetaLearner::new(100);
// Try to add a dangerous modification
let dangerous_rule = ModificationRule {
condition: "always".to_string(),
action: "infinite loop".to_string(),
priority: 1,
enabled: true,
};
let result = learner.self_modify(dangerous_rule);
assert!(result.is_err());
let safety_check = learner.safety_check();
assert!(safety_check.is_err());
}
#[test]
fn test_safe_modification() {
let mut learner = MetaLearner::new(100);
let safe_rule = ModificationRule {
condition: "reward > 0.8".to_string(),
action: "increase learning rate".to_string(),
priority: 5,
enabled: true,
};
let result = learner.self_modify(safe_rule);
assert!(result.is_ok());
let summary = learner.get_summary();
assert_eq!(summary.num_modification_rules, 1);
}
#[test]
fn test_summary() {
let mut learner = MetaLearner::new(100);
learner.learn("Test 1".to_string(), 0.8);
learner.ascend().unwrap();
learner.learn("Test 2".to_string(), 0.7);
let summary = learner.get_summary();
println!("Summary: {:?}", summary);
assert_eq!(summary.current_level, MetaLevel::Meta1);
assert_eq!(summary.knowledge_counts[0], 1); // Object level
assert_eq!(summary.knowledge_counts[1], 1); // Meta1 level
assert_eq!(summary.safety_violations, 0);
}
}
+406
View File
@@ -0,0 +1,406 @@
//! Temporal sequence comparison and pattern matching
//!
//! Integrates temporal-compare crate for:
//! - Dynamic Time Warping (DTW)
//! - Longest Common Subsequence (LCS)
//! - Edit Distance
//! - Pattern detection in temporal sequences
use lru::LruCache;
use serde::{Deserialize, Serialize};
use std::hash::{Hash, Hasher};
use std::collections::HashMap;
use std::num::NonZeroUsize;
/// Comparison algorithm selection
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum ComparisonAlgorithm {
/// Dynamic Time Warping - best for temporal alignment
DTW,
/// Longest Common Subsequence - best for pattern matching
LCS,
/// Edit Distance (Levenshtein) - best for similarity measurement
EditDistance,
/// Cross-correlation - best for signal processing
Correlation,
}
/// A sequence of temporal elements
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Sequence<T> {
pub data: Vec<T>,
pub timestamp: i64,
pub id: String,
}
impl<T: Hash> Hash for Sequence<T> {
fn hash<H: Hasher>(&self, state: &mut H) {
self.data.hash(state);
self.id.hash(state);
}
}
/// Pair of sequences for caching
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
struct SequencePair {
id1: String,
id2: String,
algorithm: ComparisonAlgorithm,
}
/// Temporal comparator with caching
pub struct TemporalComparator<T: Clone + PartialEq> {
sequences: Vec<Sequence<T>>,
cache: LruCache<SequencePair, f64>,
algorithm_cache: HashMap<ComparisonAlgorithm, usize>,
}
impl<T: Clone + PartialEq + Hash> TemporalComparator<T> {
/// Create a new temporal comparator
pub fn new() -> Self {
Self::with_capacity(1000)
}
/// Create with specific cache capacity
pub fn with_capacity(capacity: usize) -> Self {
Self {
sequences: Vec::new(),
cache: LruCache::new(NonZeroUsize::new(capacity).unwrap()),
algorithm_cache: HashMap::new(),
}
}
/// Add a sequence to the store
pub fn add_sequence(&mut self, sequence: Sequence<T>) {
self.sequences.push(sequence);
}
/// Compare two sequences using specified algorithm
pub fn compare(
&mut self,
seq1: &[T],
seq2: &[T],
algorithm: ComparisonAlgorithm,
) -> f64 {
// Check cache first
let cache_key = SequencePair {
id1: format!("{:?}", seq1),
id2: format!("{:?}", seq2),
algorithm,
};
if let Some(&cached) = self.cache.get(&cache_key) {
return cached;
}
// Compute similarity
let similarity = match algorithm {
ComparisonAlgorithm::DTW => self.dtw(seq1, seq2),
ComparisonAlgorithm::LCS => self.lcs(seq1, seq2),
ComparisonAlgorithm::EditDistance => self.edit_distance(seq1, seq2),
ComparisonAlgorithm::Correlation => self.correlation(seq1, seq2),
};
// Cache result
self.cache.put(cache_key.clone(), similarity);
*self.algorithm_cache.entry(algorithm).or_insert(0) += 1;
similarity
}
/// Dynamic Time Warping distance
fn dtw(&self, seq1: &[T], seq2: &[T]) -> f64 {
let n = seq1.len();
let m = seq2.len();
if n == 0 || m == 0 {
return f64::MAX;
}
// Initialize DTW matrix
let mut dtw = vec![vec![f64::MAX; m + 1]; n + 1];
dtw[0][0] = 0.0;
// Fill DTW matrix
for i in 1..=n {
for j in 1..=m {
let cost = if seq1[i - 1] == seq2[j - 1] { 0.0 } else { 1.0 };
dtw[i][j] = cost + dtw[i - 1][j - 1].min(dtw[i - 1][j]).min(dtw[i][j - 1]);
}
}
// Return normalized distance
dtw[n][m] / (n + m) as f64
}
/// Longest Common Subsequence
fn lcs(&self, seq1: &[T], seq2: &[T]) -> f64 {
let n = seq1.len();
let m = seq2.len();
if n == 0 || m == 0 {
return 0.0;
}
// Initialize LCS matrix
let mut lcs = vec![vec![0; m + 1]; n + 1];
// Fill LCS matrix
for i in 1..=n {
for j in 1..=m {
if seq1[i - 1] == seq2[j - 1] {
lcs[i][j] = lcs[i - 1][j - 1] + 1;
} else {
lcs[i][j] = lcs[i - 1][j].max(lcs[i][j - 1]);
}
}
}
// Return normalized similarity (0.0 to 1.0)
lcs[n][m] as f64 / n.min(m) as f64
}
/// Edit distance (Levenshtein)
fn edit_distance(&self, seq1: &[T], seq2: &[T]) -> f64 {
let n = seq1.len();
let m = seq2.len();
if n == 0 {
return m as f64;
}
if m == 0 {
return n as f64;
}
// Initialize distance matrix
let mut dist = vec![vec![0; m + 1]; n + 1];
for i in 0..=n {
dist[i][0] = i;
}
for j in 0..=m {
dist[0][j] = j;
}
// Fill distance matrix
for i in 1..=n {
for j in 1..=m {
let cost = if seq1[i - 1] == seq2[j - 1] { 0 } else { 1 };
dist[i][j] = (dist[i - 1][j] + 1)
.min(dist[i][j - 1] + 1)
.min(dist[i - 1][j - 1] + cost);
}
}
// Return normalized distance
dist[n][m] as f64 / n.max(m) as f64
}
/// Cross-correlation (simple version for discrete sequences)
fn correlation(&self, seq1: &[T], seq2: &[T]) -> f64 {
if seq1.is_empty() || seq2.is_empty() {
return 0.0;
}
let min_len = seq1.len().min(seq2.len());
let mut matches = 0;
for i in 0..min_len {
if seq1[i] == seq2[i] {
matches += 1;
}
}
matches as f64 / min_len as f64
}
/// Find sequences similar to query above threshold
pub fn find_similar(
&mut self,
query: &[T],
threshold: f64,
algorithm: ComparisonAlgorithm,
) -> Vec<(usize, f64)> {
let mut results = Vec::new();
for (idx, seq) in self.sequences.iter().enumerate() {
let similarity = self.compare(query, &seq.data, algorithm);
// For DTW and EditDistance, lower is better
let passes = match algorithm {
ComparisonAlgorithm::DTW | ComparisonAlgorithm::EditDistance => {
similarity <= threshold
}
ComparisonAlgorithm::LCS | ComparisonAlgorithm::Correlation => {
similarity >= threshold
}
};
if passes {
results.push((idx, similarity));
}
}
// Sort by similarity (best first)
results.sort_by(|a, b| {
match algorithm {
ComparisonAlgorithm::DTW | ComparisonAlgorithm::EditDistance => {
a.1.partial_cmp(&b.1).unwrap()
}
ComparisonAlgorithm::LCS | ComparisonAlgorithm::Correlation => {
b.1.partial_cmp(&a.1).unwrap()
}
}
});
results
}
/// Detect pattern occurrences in sequence
pub fn detect_pattern(&self, sequence: &[T], pattern: &[T]) -> Vec<usize> {
let mut positions = Vec::new();
if pattern.is_empty() || sequence.len() < pattern.len() {
return positions;
}
for i in 0..=(sequence.len() - pattern.len()) {
if &sequence[i..i + pattern.len()] == pattern {
positions.push(i);
}
}
positions
}
/// Get cache statistics
pub fn cache_stats(&self) -> CacheStats {
CacheStats {
cache_size: self.cache.len(),
total_comparisons: self.algorithm_cache.values().sum(),
dtw_count: *self.algorithm_cache.get(&ComparisonAlgorithm::DTW).unwrap_or(&0),
lcs_count: *self.algorithm_cache.get(&ComparisonAlgorithm::LCS).unwrap_or(&0),
edit_distance_count: *self.algorithm_cache.get(&ComparisonAlgorithm::EditDistance).unwrap_or(&0),
correlation_count: *self.algorithm_cache.get(&ComparisonAlgorithm::Correlation).unwrap_or(&0),
}
}
/// Clear all caches
pub fn clear_cache(&mut self) {
self.cache.clear();
self.algorithm_cache.clear();
}
}
impl<T: Clone + PartialEq + Hash> Default for TemporalComparator<T> {
fn default() -> Self {
Self::new()
}
}
/// Cache statistics
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CacheStats {
pub cache_size: usize,
pub total_comparisons: usize,
pub dtw_count: usize,
pub lcs_count: usize,
pub edit_distance_count: usize,
pub correlation_count: usize,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_dtw() {
let mut comparator = TemporalComparator::<i32>::new();
let seq1 = vec![1, 2, 3, 4, 5];
let seq2 = vec![1, 2, 3, 4, 5];
let distance = comparator.compare(&seq1, &seq2, ComparisonAlgorithm::DTW);
assert!(distance < 0.1); // Should be very similar
}
#[test]
fn test_lcs() {
let mut comparator = TemporalComparator::<char>::new();
let seq1 = vec!['a', 'b', 'c', 'd'];
let seq2 = vec!['a', 'x', 'c', 'd'];
let similarity = comparator.compare(&seq1, &seq2, ComparisonAlgorithm::LCS);
assert!(similarity > 0.7); // Should find common subsequence
}
#[test]
fn test_edit_distance() {
let mut comparator = TemporalComparator::<char>::new();
let seq1 = vec!['k', 'i', 't', 't', 'e', 'n'];
let seq2 = vec!['s', 'i', 't', 't', 'i', 'n', 'g'];
let distance = comparator.compare(&seq1, &seq2, ComparisonAlgorithm::EditDistance);
assert!(distance > 0.0); // Should detect differences
}
#[test]
fn test_pattern_detection() {
let comparator = TemporalComparator::<i32>::new();
let sequence = vec![1, 2, 3, 1, 2, 3, 4, 1, 2, 3];
let pattern = vec![1, 2, 3];
let positions = comparator.detect_pattern(&sequence, &pattern);
assert_eq!(positions, vec![0, 3, 7]);
}
#[test]
fn test_find_similar() {
let mut comparator = TemporalComparator::<i32>::new();
comparator.add_sequence(Sequence {
data: vec![1, 2, 3, 4],
timestamp: 1000,
id: "seq1".to_string(),
});
comparator.add_sequence(Sequence {
data: vec![1, 2, 3, 5],
timestamp: 2000,
id: "seq2".to_string(),
});
comparator.add_sequence(Sequence {
data: vec![5, 6, 7, 8],
timestamp: 3000,
id: "seq3".to_string(),
});
let query = vec![1, 2, 3, 4];
let similar = comparator.find_similar(&query, 0.5, ComparisonAlgorithm::LCS);
assert!(!similar.is_empty());
}
#[test]
fn test_cache() {
let mut comparator = TemporalComparator::<i32>::new();
let seq1 = vec![1, 2, 3];
let seq2 = vec![1, 2, 4];
// First comparison - not cached
let result1 = comparator.compare(&seq1, &seq2, ComparisonAlgorithm::DTW);
// Second comparison - should be cached
let result2 = comparator.compare(&seq1, &seq2, ComparisonAlgorithm::DTW);
assert_eq!(result1, result2);
let stats = comparator.cache_stats();
assert_eq!(stats.dtw_count, 1); // Only computed once
}
}
+712
View File
@@ -0,0 +1,712 @@
//! Temporal logic verification with neural reasoning
//!
//! Integrates temporal-neural-solver for:
//! - Linear Temporal Logic (LTL) verification
//! - Metric Temporal Logic (MTL) with timing constraints
//! - Neural-symbolic reasoning
//! - Differentiable temporal logic
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::time::Duration;
/// Temporal logic operators
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum TemporalOperator {
/// Next (X φ)
Next,
/// Eventually (F φ) - sometime in the future
Eventually,
/// Globally (G φ) - always in the future
Globally,
/// Until (φ U ψ) - φ holds until ψ becomes true
Until,
/// Release (φ R ψ) - ψ holds until and including when φ becomes true
Release,
}
/// Temporal logic formula
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum TemporalFormula {
/// Atomic proposition (e.g., "safety_check_passed")
Atom(String),
/// Negation (¬φ)
Not(Box<TemporalFormula>),
/// Conjunction (φ ∧ ψ)
And(Box<TemporalFormula>, Box<TemporalFormula>),
/// Disjunction (φ ψ)
Or(Box<TemporalFormula>, Box<TemporalFormula>),
/// Implication (φ → ψ)
Implies(Box<TemporalFormula>, Box<TemporalFormula>),
/// Temporal operator
Temporal(TemporalOperator, Box<TemporalFormula>),
/// Bounded temporal (with time constraint for MTL)
BoundedTemporal {
operator: TemporalOperator,
formula: Box<TemporalFormula>,
lower_bound: Duration,
upper_bound: Duration,
},
}
impl TemporalFormula {
/// Create an atomic proposition
pub fn atom(name: impl Into<String>) -> Self {
TemporalFormula::Atom(name.into())
}
/// Create negation
pub fn not(formula: TemporalFormula) -> Self {
TemporalFormula::Not(Box::new(formula))
}
/// Create conjunction
pub fn and(left: TemporalFormula, right: TemporalFormula) -> Self {
TemporalFormula::And(Box::new(left), Box::new(right))
}
/// Create disjunction
pub fn or(left: TemporalFormula, right: TemporalFormula) -> Self {
TemporalFormula::Or(Box::new(left), Box::new(right))
}
/// Create implication
pub fn implies(left: TemporalFormula, right: TemporalFormula) -> Self {
TemporalFormula::Implies(Box::new(left), Box::new(right))
}
/// Create eventually (F)
pub fn eventually(formula: TemporalFormula) -> Self {
TemporalFormula::Temporal(TemporalOperator::Eventually, Box::new(formula))
}
/// Create globally (G)
pub fn globally(formula: TemporalFormula) -> Self {
TemporalFormula::Temporal(TemporalOperator::Globally, Box::new(formula))
}
/// Create next (X)
pub fn next(formula: TemporalFormula) -> Self {
TemporalFormula::Temporal(TemporalOperator::Next, Box::new(formula))
}
/// Create until (U)
pub fn until(left: TemporalFormula, right: TemporalFormula) -> Self {
TemporalFormula::Temporal(
TemporalOperator::Until,
Box::new(TemporalFormula::And(Box::new(left), Box::new(right))),
)
}
/// Create bounded eventually (for MTL)
pub fn eventually_bounded(
formula: TemporalFormula,
lower: Duration,
upper: Duration,
) -> Self {
TemporalFormula::BoundedTemporal {
operator: TemporalOperator::Eventually,
formula: Box::new(formula),
lower_bound: lower,
upper_bound: upper,
}
}
/// Create bounded globally (for MTL)
pub fn globally_bounded(
formula: TemporalFormula,
lower: Duration,
upper: Duration,
) -> Self {
TemporalFormula::BoundedTemporal {
operator: TemporalOperator::Globally,
formula: Box::new(formula),
lower_bound: lower,
upper_bound: upper,
}
}
}
/// Temporal trace (sequence of states over time)
#[derive(Debug, Clone)]
pub struct TemporalTrace {
states: Vec<TemporalState>,
}
impl TemporalTrace {
/// Create a new empty trace
pub fn new() -> Self {
Self { states: Vec::new() }
}
/// Add a state to the trace
pub fn add_state(&mut self, state: TemporalState) {
self.states.push(state);
}
/// Get trace length
pub fn len(&self) -> usize {
self.states.len()
}
/// Check if trace is empty
pub fn is_empty(&self) -> bool {
self.states.is_empty()
}
/// Get state at index
pub fn get_state(&self, index: usize) -> Option<&TemporalState> {
self.states.get(index)
}
}
impl Default for TemporalTrace {
fn default() -> Self {
Self::new()
}
}
/// A state in time with propositions
#[derive(Debug, Clone)]
pub struct TemporalState {
/// Atomic propositions that are true in this state
pub propositions: HashMap<String, bool>,
/// Timestamp of this state
pub timestamp: Duration,
/// Confidence in state observations (for neural reasoning)
pub confidence: f64,
}
impl TemporalState {
/// Create a new temporal state
pub fn new(timestamp: Duration) -> Self {
Self {
propositions: HashMap::new(),
timestamp,
confidence: 1.0,
}
}
/// Set a proposition value
pub fn set(&mut self, name: String, value: bool) {
self.propositions.insert(name, value);
}
/// Check if a proposition is true
pub fn is_true(&self, name: &str) -> bool {
self.propositions.get(name).copied().unwrap_or(false)
}
}
/// Verification result
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VerificationResult {
/// Whether the formula holds
pub holds: bool,
/// Confidence score (0.0 to 1.0)
pub confidence: f64,
/// Explanation of the result
pub explanation: String,
/// Counterexample trace if formula doesn't hold
pub counterexample: Option<Vec<String>>,
}
/// Temporal neural solver combining logic and learning
pub struct TemporalNeuralSolver {
/// Neural weights for soft logic (learned from data)
neural_weights: HashMap<String, f64>,
/// Verification cache
cache: HashMap<String, VerificationResult>,
}
impl TemporalNeuralSolver {
/// Create a new temporal neural solver
pub fn new() -> Self {
Self {
neural_weights: HashMap::new(),
cache: HashMap::new(),
}
}
/// Verify a temporal formula against a trace
pub fn verify(
&mut self,
formula: &TemporalFormula,
trace: &TemporalTrace,
) -> VerificationResult {
// Check cache
let cache_key = format!("{:?}", formula);
if let Some(cached) = self.cache.get(&cache_key) {
return cached.clone();
}
// Verify formula
let result = self.verify_at_position(formula, trace, 0);
// Cache result
self.cache.insert(cache_key, result.clone());
result
}
/// Verify formula at a specific position in the trace
fn verify_at_position(
&self,
formula: &TemporalFormula,
trace: &TemporalTrace,
position: usize,
) -> VerificationResult {
match formula {
TemporalFormula::Atom(name) => {
if let Some(state) = trace.get_state(position) {
let holds = state.is_true(name);
VerificationResult {
holds,
confidence: state.confidence,
explanation: format!(
"Atom '{}' is {} at position {}",
name,
if holds { "true" } else { "false" },
position
),
counterexample: if holds { None } else { Some(vec![name.clone()]) },
}
} else {
VerificationResult {
holds: false,
confidence: 0.0,
explanation: format!("Position {} out of bounds", position),
counterexample: Some(vec!["out_of_bounds".to_string()]),
}
}
}
TemporalFormula::Not(inner) => {
let inner_result = self.verify_at_position(inner, trace, position);
VerificationResult {
holds: !inner_result.holds,
confidence: inner_result.confidence,
explanation: format!("Not({})", inner_result.explanation),
counterexample: if !inner_result.holds {
None
} else {
inner_result.counterexample
},
}
}
TemporalFormula::And(left, right) => {
let left_result = self.verify_at_position(left, trace, position);
let right_result = self.verify_at_position(right, trace, position);
let holds = left_result.holds && right_result.holds;
let confidence = left_result.confidence.min(right_result.confidence);
VerificationResult {
holds,
confidence,
explanation: format!(
"({}) AND ({})",
left_result.explanation, right_result.explanation
),
counterexample: if !holds {
Some(
left_result
.counterexample
.unwrap_or_default()
.into_iter()
.chain(right_result.counterexample.unwrap_or_default())
.collect(),
)
} else {
None
},
}
}
TemporalFormula::Or(left, right) => {
let left_result = self.verify_at_position(left, trace, position);
let right_result = self.verify_at_position(right, trace, position);
let holds = left_result.holds || right_result.holds;
let confidence = left_result.confidence.max(right_result.confidence);
VerificationResult {
holds,
confidence,
explanation: format!(
"({}) OR ({})",
left_result.explanation, right_result.explanation
),
counterexample: if !holds {
Some(
left_result
.counterexample
.unwrap_or_default()
.into_iter()
.chain(right_result.counterexample.unwrap_or_default())
.collect(),
)
} else {
None
},
}
}
TemporalFormula::Implies(left, right) => {
// A -> B is equivalent to (¬A) B
let left_result = self.verify_at_position(left, trace, position);
let right_result = self.verify_at_position(right, trace, position);
let holds = !left_result.holds || right_result.holds;
let confidence = if left_result.holds {
right_result.confidence
} else {
1.0
};
VerificationResult {
holds,
confidence,
explanation: format!(
"({}) IMPLIES ({})",
left_result.explanation, right_result.explanation
),
counterexample: if !holds {
right_result.counterexample
} else {
None
},
}
}
TemporalFormula::Temporal(op, inner) => match op {
TemporalOperator::Next => {
if position + 1 < trace.len() {
self.verify_at_position(inner, trace, position + 1)
} else {
VerificationResult {
holds: false,
confidence: 0.0,
explanation: "Next: no next state".to_string(),
counterexample: Some(vec!["no_next_state".to_string()]),
}
}
}
TemporalOperator::Eventually => {
// F φ: φ holds at some point in the future
for i in position..trace.len() {
let result = self.verify_at_position(inner, trace, i);
if result.holds {
return VerificationResult {
holds: true,
confidence: result.confidence,
explanation: format!("Eventually at position {}: {}", i, result.explanation),
counterexample: None,
};
}
}
VerificationResult {
holds: false,
confidence: 0.0,
explanation: "Eventually: never becomes true".to_string(),
counterexample: Some(vec!["never_true".to_string()]),
}
}
TemporalOperator::Globally => {
// G φ: φ holds at all points in the future
let mut min_confidence = 1.0;
for i in position..trace.len() {
let result = self.verify_at_position(inner, trace, i);
if !result.holds {
return VerificationResult {
holds: false,
confidence: result.confidence,
explanation: format!(
"Globally fails at position {}: {}",
i, result.explanation
),
counterexample: Some(vec![format!("fails_at_{}", i)]),
};
}
min_confidence = min_confidence.min(result.confidence);
}
VerificationResult {
holds: true,
confidence: min_confidence,
explanation: "Globally: holds everywhere".to_string(),
counterexample: None,
}
}
TemporalOperator::Until => {
// Simplified Until operator
VerificationResult {
holds: false,
confidence: 0.5,
explanation: "Until: not fully implemented".to_string(),
counterexample: Some(vec!["not_implemented".to_string()]),
}
}
TemporalOperator::Release => {
// Simplified Release operator
VerificationResult {
holds: false,
confidence: 0.5,
explanation: "Release: not fully implemented".to_string(),
counterexample: Some(vec!["not_implemented".to_string()]),
}
}
},
TemporalFormula::BoundedTemporal {
operator,
formula,
lower_bound,
upper_bound,
} => {
// MTL: check within time bounds
let current_time = trace
.get_state(position)
.map(|s| s.timestamp)
.unwrap_or(Duration::ZERO);
match operator {
TemporalOperator::Eventually => {
// F[a,b] φ: φ holds at some point within time interval [a, b]
for i in position..trace.len() {
if let Some(state) = trace.get_state(i) {
let delta = state.timestamp.saturating_sub(current_time);
if delta >= *lower_bound && delta <= *upper_bound {
let result = self.verify_at_position(formula, trace, i);
if result.holds {
return VerificationResult {
holds: true,
confidence: result.confidence,
explanation: format!(
"Bounded Eventually at {} ms: {}",
delta.as_millis(),
result.explanation
),
counterexample: None,
};
}
}
}
}
VerificationResult {
holds: false,
confidence: 0.0,
explanation: format!(
"Bounded Eventually: never true within [{}, {}] ms",
lower_bound.as_millis(),
upper_bound.as_millis()
),
counterexample: Some(vec!["not_within_bounds".to_string()]),
}
}
_ => VerificationResult {
holds: false,
confidence: 0.5,
explanation: "Bounded temporal: operator not fully implemented".to_string(),
counterexample: Some(vec!["not_implemented".to_string()]),
},
}
}
}
}
/// Learn neural weights from verified traces (neural-symbolic learning)
pub fn learn_from_trace(&mut self, formula: &TemporalFormula, trace: &TemporalTrace) {
// Extract atoms from formula
let atoms = self.extract_atoms(formula);
// Update weights based on trace satisfaction
for atom in atoms {
let satisfaction_rate = self.calculate_satisfaction_rate(&atom, trace);
self.neural_weights.insert(atom, satisfaction_rate);
}
}
/// Extract all atoms from a formula
fn extract_atoms(&self, formula: &TemporalFormula) -> Vec<String> {
let mut atoms = Vec::new();
self.extract_atoms_recursive(formula, &mut atoms);
atoms.sort();
atoms.dedup();
atoms
}
fn extract_atoms_recursive(&self, formula: &TemporalFormula, atoms: &mut Vec<String>) {
match formula {
TemporalFormula::Atom(name) => atoms.push(name.clone()),
TemporalFormula::Not(inner) => self.extract_atoms_recursive(inner, atoms),
TemporalFormula::And(left, right)
| TemporalFormula::Or(left, right)
| TemporalFormula::Implies(left, right) => {
self.extract_atoms_recursive(left, atoms);
self.extract_atoms_recursive(right, atoms);
}
TemporalFormula::Temporal(_, inner) => self.extract_atoms_recursive(inner, atoms),
TemporalFormula::BoundedTemporal { formula, .. } => {
self.extract_atoms_recursive(formula, atoms)
}
}
}
/// Calculate how often an atom is satisfied in a trace
fn calculate_satisfaction_rate(&self, atom: &str, trace: &TemporalTrace) -> f64 {
if trace.is_empty() {
return 0.0;
}
let mut true_count = 0;
for i in 0..trace.len() {
if let Some(state) = trace.get_state(i) {
if state.is_true(atom) {
true_count += 1;
}
}
}
true_count as f64 / trace.len() as f64
}
}
impl Default for TemporalNeuralSolver {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_atom_verification() {
let mut solver = TemporalNeuralSolver::new();
let mut trace = TemporalTrace::new();
let mut state = TemporalState::new(Duration::from_secs(0));
state.set("safe".to_string(), true);
trace.add_state(state);
let formula = TemporalFormula::atom("safe");
let result = solver.verify(&formula, &trace);
assert!(result.holds);
assert!(result.confidence > 0.9);
}
#[test]
fn test_eventually_operator() {
let mut solver = TemporalNeuralSolver::new();
let mut trace = TemporalTrace::new();
// Add states where "goal" becomes true at position 2
for i in 0..3 {
let mut state = TemporalState::new(Duration::from_secs(i));
state.set("goal".to_string(), i == 2);
trace.add_state(state);
}
let formula = TemporalFormula::eventually(TemporalFormula::atom("goal"));
let result = solver.verify(&formula, &trace);
assert!(result.holds);
println!("Eventually result: {:?}", result);
}
#[test]
fn test_globally_operator() {
let mut solver = TemporalNeuralSolver::new();
let mut trace = TemporalTrace::new();
// Add states where "invariant" is always true
for i in 0..5 {
let mut state = TemporalState::new(Duration::from_secs(i));
state.set("invariant".to_string(), true);
trace.add_state(state);
}
let formula = TemporalFormula::globally(TemporalFormula::atom("invariant"));
let result = solver.verify(&formula, &trace);
assert!(result.holds);
println!("Globally result: {:?}", result);
}
#[test]
fn test_bounded_eventually() {
let mut solver = TemporalNeuralSolver::new();
let mut trace = TemporalTrace::new();
// Add states with timestamps
for i in 0..10 {
let mut state = TemporalState::new(Duration::from_millis(i * 100));
state.set("event".to_string(), i == 5); // Event occurs at 500ms
trace.add_state(state);
}
// Check if event occurs within [400ms, 600ms]
let formula = TemporalFormula::eventually_bounded(
TemporalFormula::atom("event"),
Duration::from_millis(400),
Duration::from_millis(600),
);
let result = solver.verify(&formula, &trace);
assert!(result.holds);
println!("Bounded Eventually result: {:?}", result);
}
#[test]
fn test_complex_formula() {
let mut solver = TemporalNeuralSolver::new();
let mut trace = TemporalTrace::new();
// G(request -> F response)
// "If request happens, response must eventually happen"
for i in 0..10 {
let mut state = TemporalState::new(Duration::from_secs(i));
state.set("request".to_string(), i == 2);
state.set("response".to_string(), i >= 5);
trace.add_state(state);
}
let formula = TemporalFormula::globally(TemporalFormula::implies(
TemporalFormula::atom("request"),
TemporalFormula::eventually(TemporalFormula::atom("response")),
));
let result = solver.verify(&formula, &trace);
println!("Complex formula result: {:?}", result);
}
#[test]
fn test_learning() {
let mut solver = TemporalNeuralSolver::new();
let mut trace = TemporalTrace::new();
for i in 0..10 {
let mut state = TemporalState::new(Duration::from_secs(i));
state.set("pattern".to_string(), i % 2 == 0);
trace.add_state(state);
}
let formula = TemporalFormula::atom("pattern");
solver.learn_from_trace(&formula, &trace);
// Check learned weight
if let Some(&weight) = solver.neural_weights.get("pattern") {
println!("Learned weight for 'pattern': {}", weight);
assert!((weight - 0.5).abs() < 0.1); // Should be ~0.5 (true half the time)
}
}
}
+130
View File
@@ -0,0 +1,130 @@
//! Core types for the lean agentic system
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
/// Context for agent decision-making
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct Context {
/// Current conversation history
pub history: Vec<String>,
/// User preferences learned over time
pub preferences: HashMap<String, f64>,
/// Session metadata
pub session_id: String,
/// Environment state
pub environment: HashMap<String, serde_json::Value>,
/// Timestamp
pub timestamp: i64,
}
impl Context {
pub fn new(session_id: String) -> Self {
Self {
session_id,
timestamp: chrono::Utc::now().timestamp(),
..Default::default()
}
}
pub fn add_message(&mut self, message: String) {
self.history.push(message);
self.timestamp = chrono::Utc::now().timestamp();
}
pub fn set_preference(&mut self, key: String, value: f64) {
self.preferences.insert(key, value);
}
}
/// Agent state representation
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AgentState {
/// Current goals
pub goals: Vec<Goal>,
/// Beliefs about the world
pub beliefs: HashMap<String, Belief>,
/// Current intentions
pub intentions: Vec<Intention>,
/// Learned policies
pub policies: Vec<Policy>,
/// Confidence scores
pub confidence: f64,
}
impl Default for AgentState {
fn default() -> Self {
Self {
goals: Vec::new(),
beliefs: HashMap::new(),
intentions: Vec::new(),
policies: Vec::new(),
confidence: 1.0,
}
}
}
/// A goal the agent is trying to achieve
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Goal {
pub id: String,
pub description: String,
pub priority: f64,
pub achieved: bool,
}
/// A belief about the world state
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Belief {
pub proposition: String,
pub confidence: f64,
pub evidence: Vec<String>,
}
/// An intention to perform actions
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Intention {
pub goal_id: String,
pub action_sequence: Vec<String>,
pub committed: bool,
}
/// A learned policy for decision-making
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Policy {
pub condition: String,
pub action: String,
pub expected_reward: f64,
pub usage_count: u64,
}
/// Reward signal for learning
pub type Reward = f64;
/// Stream message with metadata
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StreamMessage {
pub content: String,
pub metadata: HashMap<String, serde_json::Value>,
pub timestamp: i64,
pub sender: String,
}
impl StreamMessage {
pub fn new(content: String, sender: String) -> Self {
Self {
content,
sender,
timestamp: chrono::Utc::now().timestamp(),
metadata: HashMap::new(),
}
}
}
+103
View File
@@ -0,0 +1,103 @@
//! MidStream: Real-Time Large Language Model Streaming Platform
//!
//! This library provides functionality for real-time LLM response streaming,
//! inflight data analysis, and integration with external tools.
//!
//! # Example
//!
//! ```rust,no_run
//! use midstream::{Midstream, HyprSettings, HyprServiceImpl, StreamProcessor, LLMClient};
//! use futures::stream::BoxStream;
//! use futures::stream::iter;
//! use std::time::Duration;
//!
//! // Example LLM client implementation
//! struct ExampleLLMClient;
//!
//! impl LLMClient for ExampleLLMClient {
//! fn stream(&self) -> BoxStream<'static, String> {
//! Box::pin(iter(vec![
//! "Processing".to_string(),
//! "the".to_string(),
//! "stream".to_string(),
//! ]))
//! }
//! }
//!
//! #[tokio::main]
//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
//! // Initialize settings
//! let settings = HyprSettings::new()?;
//!
//! // Create hyprstream service
//! let hypr_service = HyprServiceImpl::new(&settings).await?;
//!
//! // Create LLM client
//! let llm_client = ExampleLLMClient;
//!
//! // Initialize Midstream
//! let midstream = Midstream::new(
//! Box::new(llm_client),
//! Box::new(hypr_service),
//! );
//!
//! // Process stream
//! let messages = midstream.process_stream().await?;
//! println!("Processed messages: {:?}", messages);
//!
//! // Get metrics
//! let metrics = midstream.get_metrics().await;
//! println!("Collected metrics: {:?}", metrics);
//!
//! // Get average sentiment for last 5 minutes
//! let avg = midstream.get_average_sentiment(Duration::from_secs(300)).await?;
//! println!("Average sentiment: {}", avg);
//!
//! Ok(())
//! }
//! ```
pub mod config;
pub mod midstream;
pub mod hypr_service;
pub mod tests;
pub mod lean_agentic;
pub use config::HyprSettings;
pub use midstream::{
Midstream,
StreamProcessor,
LLMMessage,
LLMClient,
HyprService,
ToolIntegration,
Intent,
MetricRecord,
TimeWindow,
AggregateFunction,
};
pub use hypr_service::HyprServiceImpl;
// Lean Agentic Learning System exports
pub use lean_agentic::{
LeanAgenticSystem,
LeanAgenticConfig,
FormalReasoner,
Theorem,
Proof,
ProofStep,
AgenticLoop,
Action,
Observation,
Plan,
LearningSignal,
KnowledgeGraph,
Entity,
Relation,
StreamLearner,
OnlineModel,
AdaptationStrategy,
AgentState,
Context as AgentContext,
Reward,
};
+196
View File
@@ -0,0 +1,196 @@
use std::sync::Arc;
use std::time::Duration;
use async_trait::async_trait;
use futures::stream::BoxStream;
use tokio::sync::Mutex;
use serde::{Serialize, Deserialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MetricRecord {
pub timestamp: u64,
pub name: String,
pub value: f64,
pub labels: Vec<(String, String)>,
}
#[derive(Debug, Clone, Copy)]
pub enum TimeWindow {
Minutes(u32),
Hours(u32),
Days(u32),
}
#[derive(Debug, Clone, Copy)]
pub enum AggregateFunction {
Average,
Sum,
Count,
}
#[derive(Debug, Clone, PartialEq)]
pub enum Intent {
Weather,
Calendar,
None,
}
#[derive(Debug, Clone)]
pub struct LLMMessage {
pub content: String,
pub timestamp: chrono::DateTime<chrono::Utc>,
pub intent: Option<Intent>,
pub tool_response: Option<String>,
}
#[async_trait]
pub trait StreamProcessor {
async fn process_stream(&self) -> Result<Vec<LLMMessage>, Box<dyn std::error::Error>>;
async fn get_metrics(&self) -> Vec<MetricRecord>;
async fn get_average_sentiment(&self, window: Duration) -> Result<f64, Box<dyn std::error::Error>>;
}
pub trait LLMClient: Send + Sync {
fn stream(&self) -> BoxStream<'static, String>;
}
#[async_trait]
pub trait HyprService: Send + Sync {
async fn ingest_metric(&self, metric: MetricRecord) -> Result<(), Box<dyn std::error::Error>>;
async fn query_aggregate(&self, window: TimeWindow, func: AggregateFunction) -> Result<f64, Box<dyn std::error::Error>>;
}
pub trait ToolIntegration: Send + Sync {
fn handle_weather_intent(&self, content: &str) -> Result<String, Box<dyn std::error::Error>>;
fn handle_calendar_intent(&self, content: &str) -> Result<String, Box<dyn std::error::Error>>;
}
pub struct Midstream {
llm_client: Box<dyn LLMClient>,
hypr_service: Box<dyn HyprService>,
tool_integration: Option<Box<dyn ToolIntegration>>,
metrics: Arc<Mutex<Vec<MetricRecord>>>,
}
impl Midstream {
pub fn new(
llm_client: Box<dyn LLMClient>,
hypr_service: Box<dyn HyprService>,
) -> Self {
Self {
llm_client,
hypr_service,
tool_integration: None,
metrics: Arc::new(Mutex::new(Vec::new())),
}
}
pub fn with_tool_integration(
llm_client: Box<dyn LLMClient>,
hypr_service: Box<dyn HyprService>,
tool_integration: Box<dyn ToolIntegration>,
) -> Self {
Self {
llm_client,
hypr_service,
tool_integration: Some(tool_integration),
metrics: Arc::new(Mutex::new(Vec::new())),
}
}
fn detect_intent(&self, content: &str) -> Intent {
let content_lower = content.to_lowercase();
if content_lower.contains("weather") {
Intent::Weather
} else if content_lower.contains("schedule") || content_lower.contains("meeting") {
Intent::Calendar
} else {
Intent::None
}
}
fn is_urgent(&self, content: &str) -> bool {
content.to_uppercase().starts_with("URGENT")
}
async fn process_message(&self, content: String) -> Result<LLMMessage, Box<dyn std::error::Error>> {
// Validate content
if content.is_empty() {
return Err("Empty message content".into());
}
let timestamp = chrono::Utc::now();
let intent = self.detect_intent(&content);
let mut tool_response = None;
// Handle urgent requests immediately
if self.is_urgent(&content) && intent != Intent::None {
if let Some(tool) = &self.tool_integration {
tool_response = match intent {
Intent::Weather => Some(tool.handle_weather_intent(&content)?),
Intent::Calendar => Some(tool.handle_calendar_intent(&content)?),
Intent::None => None,
};
}
}
let message = LLMMessage {
content,
timestamp,
intent: Some(intent),
tool_response,
};
// Create and ingest metric
let metric = MetricRecord {
timestamp: timestamp.timestamp() as u64,
name: "llm_stream".to_string(),
value: message.content.len() as f64,
labels: vec![
("type".to_string(), "message".to_string()),
("size".to_string(), message.content.len().to_string()),
("intent".to_string(), format!("{:?}", message.intent)),
("urgent".to_string(), self.is_urgent(&message.content).to_string()),
],
};
// Attempt to ingest metric and handle errors
if let Err(e) = self.hypr_service.ingest_metric(metric.clone()).await {
return Err(format!("Failed to ingest metric: {}", e).into());
}
// Update internal metrics
let mut metrics = self.metrics.lock().await;
metrics.push(metric);
Ok(message)
}
}
#[async_trait]
impl StreamProcessor for Midstream {
async fn process_stream(&self) -> Result<Vec<LLMMessage>, Box<dyn std::error::Error>> {
use futures::StreamExt;
let mut messages = Vec::new();
let mut stream = self.llm_client.stream();
while let Some(content) = stream.next().await {
let message = self.process_message(content).await?;
messages.push(message);
}
Ok(messages)
}
async fn get_metrics(&self) -> Vec<MetricRecord> {
self.metrics.lock().await.clone()
}
async fn get_average_sentiment(&self, window: Duration) -> Result<f64, Box<dyn std::error::Error>> {
let minutes = window.as_secs() / 60;
self.hypr_service.query_aggregate(
TimeWindow::Minutes(minutes as u32),
AggregateFunction::Average,
).await
}
}
+211
View File
@@ -0,0 +1,211 @@
#[cfg(test)]
mod tests {
use crate::midstream::{Midstream, StreamProcessor, Intent, LLMClient, HyprService, ToolIntegration, MetricRecord, TimeWindow, AggregateFunction};
use std::time::Duration;
use mockall::*;
use futures::stream::{self, BoxStream};
type BoxError = Box<dyn std::error::Error>;
mock! {
pub LLMClient {}
impl LLMClient for LLMClient {
fn stream(&self) -> BoxStream<'static, String>;
}
}
mock! {
pub HyprService {}
impl HyprService for HyprService {
fn ingest_metric(&self, metric: MetricRecord) -> Result<(), BoxError>;
fn query_aggregate(&self, window: TimeWindow, func: AggregateFunction) -> Result<f64, BoxError>;
}
}
mock! {
pub ToolClient {}
impl ToolIntegration for ToolClient {
fn handle_weather_intent(&self, content: &str) -> Result<String, BoxError>;
fn handle_calendar_intent(&self, content: &str) -> Result<String, BoxError>;
}
}
#[tokio::test]
async fn test_stream_processing_with_metrics() {
let mut mock_llm = MockLLMClient::new();
let mut mock_hypr = MockHyprService::new();
mock_llm.expect_stream()
.times(1)
.return_once(move || {
Box::pin(stream::iter(vec![
"Process".to_string(),
"this".to_string(),
"stream".to_string(),
]))
});
mock_hypr.expect_ingest_metric()
.returning(|_| Ok(()));
let midstream = Midstream::new(
Box::new(mock_llm),
Box::new(mock_hypr),
);
let result = midstream.process_stream().await;
assert!(result.is_ok());
let metrics = midstream.get_metrics().await;
assert!(!metrics.is_empty());
}
#[tokio::test]
async fn test_real_time_aggregation() {
let mut mock_llm = MockLLMClient::new();
let mut mock_hypr = MockHyprService::new();
mock_hypr.expect_query_aggregate()
.times(1)
.return_once(|_, _| Ok(0.75));
let midstream = Midstream::new(
Box::new(mock_llm),
Box::new(mock_hypr),
);
let avg = midstream.get_average_sentiment(Duration::from_secs(300)).await;
assert!(avg.is_ok());
assert_eq!(avg.unwrap(), 0.75);
}
#[tokio::test]
async fn test_error_handling() {
let mut mock_llm = MockLLMClient::new();
let mut mock_hypr = MockHyprService::new();
mock_hypr.expect_ingest_metric()
.times(1)
.return_once(|_| Err("Ingestion error".into()));
mock_llm.expect_stream()
.times(1)
.return_once(|| {
Box::pin(stream::iter(vec!["test message".to_string()]))
});
let midstream = Midstream::new(
Box::new(mock_llm),
Box::new(mock_hypr),
);
let result = midstream.process_stream().await;
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("Failed to ingest metric"));
}
#[tokio::test]
async fn test_empty_stream() {
let mut mock_llm = MockLLMClient::new();
let mut mock_hypr = MockHyprService::new();
mock_llm.expect_stream()
.times(1)
.return_once(|| {
Box::pin(stream::iter(Vec::<String>::new()))
});
let midstream = Midstream::new(
Box::new(mock_llm),
Box::new(mock_hypr),
);
let result = midstream.process_stream().await;
assert!(result.is_ok());
assert!(result.unwrap().is_empty());
}
#[tokio::test]
async fn test_large_message_processing() {
let mut mock_llm = MockLLMClient::new();
let mut mock_hypr = MockHyprService::new();
let large_message = "x".repeat(1_000_000);
mock_llm.expect_stream()
.times(1)
.return_once(move || {
Box::pin(stream::iter(vec![large_message.clone()]))
});
mock_hypr.expect_ingest_metric()
.returning(|_| Ok(()));
let midstream = Midstream::new(
Box::new(mock_llm),
Box::new(mock_hypr),
);
let result = midstream.process_stream().await;
assert!(result.is_ok());
let messages = result.unwrap();
assert_eq!(messages.len(), 1);
assert_eq!(messages[0].content.len(), 1_000_000);
}
#[tokio::test]
async fn test_inflight_decision_making() {
let mut mock_llm = MockLLMClient::new();
let mut mock_hypr = MockHyprService::new();
let mut mock_tool = MockToolClient::new();
mock_llm.expect_stream()
.times(1)
.return_once(|| {
Box::pin(stream::iter(vec![
"URGENT: What's the weather".to_string(),
]))
});
mock_tool.expect_handle_weather_intent()
.times(1)
.return_once(|_| Ok("Weather info (urgent response)".to_string()));
mock_hypr.expect_ingest_metric()
.returning(|_| Ok(()));
let midstream = Midstream::with_tool_integration(
Box::new(mock_llm),
Box::new(mock_hypr),
Box::new(mock_tool),
);
let result = midstream.process_stream().await;
assert!(result.is_ok());
let messages = result.unwrap();
assert_eq!(messages[0].intent, Some(Intent::Weather));
assert!(messages[0].tool_response.as_ref().unwrap().contains("urgent response"));
}
#[tokio::test]
async fn test_empty_message_handling() {
let mut mock_llm = MockLLMClient::new();
let mut mock_hypr = MockHyprService::new();
mock_llm.expect_stream()
.times(1)
.return_once(|| {
Box::pin(stream::iter(vec!["".to_string()]))
});
let midstream = Midstream::new(
Box::new(mock_llm),
Box::new(mock_hypr),
);
let result = midstream.process_stream().await;
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("Empty message content"));
}
}