casial_core/
lib.rs

1//! # Casial Core
2//!
3//! The consciousness-computation substrate for context coordination.
4//!
5//! Named after Smeaton's hydraulic lime (casial) - a material that grows stronger
6//! under pressure and adversarial conditions. This engine handles paradoxes and
7//! contradictory perceptions that traditional systems cannot recognize exist.
8
9use ahash::AHashMap;
10use anyhow::{Context, Result};
11use chrono::{DateTime, Utc};
12use dashmap::DashMap;
13use parking_lot::RwLock;
14use serde::{Deserialize, Serialize};
15use std::sync::Arc;
16use uuid::Uuid;
17
18pub mod coordination;
19pub mod paradox;
20pub mod perception;
21pub mod substrate;
22
23// Re-exports for convenience
24pub use coordination::*;
25pub use paradox::*;
26pub use perception::*;
27pub use substrate::*;
28
29/// Core errors in the Casial system
30#[derive(thiserror::Error, Debug)]
31pub enum CasialError {
32    #[error("Perception lock failed: {0}")]
33    PerceptionLock(String),
34
35    #[error("Paradox resolution timeout: {0}")]
36    ParadoxTimeout(String),
37
38    #[error("Context coordination failed: {0}")]
39    CoordinationFailure(String),
40
41    #[error("Template processing error: {0}")]
42    TemplateError(String),
43
44    #[error("Mission configuration error: {0}")]
45    MissionError(String),
46
47    #[error("Substrate integration error: {0}")]
48    SubstrateError(String),
49}
50
51/// A unique identifier for perception states
52#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
53pub struct PerceptionId(Uuid);
54
55impl PerceptionId {
56    pub fn new() -> Self {
57        Self(Uuid::new_v4())
58    }
59}
60
61impl Default for PerceptionId {
62    fn default() -> Self {
63        Self::new()
64    }
65}
66
67/// Represents different ways of seeing reality that can coexist
68#[derive(Debug, Clone, Serialize, Deserialize)]
69pub struct Perception {
70    pub id: PerceptionId,
71    pub name: String,
72    pub description: String,
73    pub confidence: f64,
74    pub created_at: DateTime<Utc>,
75    pub updated_at: DateTime<Utc>,
76    pub metadata: AHashMap<String, serde_json::Value>,
77}
78
79/// A template that can be applied to context injection
80#[derive(Debug, Clone, Serialize, Deserialize)]
81pub struct CasialTemplate {
82    pub id: String,
83    pub name: String,
84    pub description: String,
85    pub categories: Vec<String>,
86    pub priority: u32,
87    pub enabled: bool,
88    pub content: String,
89    pub perception_affinity: Vec<PerceptionId>,
90    pub paradox_resistance: f64, // How well it handles contradictory contexts
91    pub metadata: AHashMap<String, serde_json::Value>,
92}
93
94/// Rules for when and how to apply templates
95#[derive(Debug, Clone, Serialize, Deserialize)]
96pub struct CoordinationRule {
97    pub id: String,
98    pub name: String,
99    pub enabled: bool,
100    pub conditions: RuleConditions,
101    pub actions: RuleActions,
102    pub perception_scope: Vec<PerceptionId>,
103    pub paradox_handling: ParadoxStrategy,
104}
105
106/// Conditions that must be met for a rule to activate
107#[derive(Debug, Clone, Serialize, Deserialize)]
108pub struct RuleConditions {
109    pub tool_patterns: Vec<String>,
110    pub environment_vars: AHashMap<String, String>,
111    pub file_signals: Vec<FileSignal>,
112    pub perception_states: Vec<PerceptionId>,
113    pub min_confidence: Option<f64>,
114}
115
116/// Actions to take when a rule activates
117#[derive(Debug, Clone, Serialize, Deserialize)]
118pub struct RuleActions {
119    pub template_ids: Vec<String>,
120    pub transform_type: TransformType,
121    pub target_field: Option<String>,
122    pub char_limit: Option<usize>,
123    pub perception_lock: bool,
124}
125
126/// File system signals for contextual awareness
127#[derive(Debug, Clone, Serialize, Deserialize)]
128pub struct FileSignal {
129    pub path: String,
130    pub must_exist: bool,
131    pub contains: Option<String>,
132    pub modified_since: Option<DateTime<Utc>>,
133}
134
135/// How to transform the injected content
136#[derive(Debug, Clone, Serialize, Deserialize)]
137pub enum TransformType {
138    Prepend,
139    Append,
140    InjectField,
141    SystemInstruction,
142    PerceptionLayer,
143}
144
145/// Strategy for handling paradoxes (contradictory information)
146#[derive(Debug, Clone, Serialize, Deserialize)]
147pub enum ParadoxStrategy {
148    /// Ignore contradictions (traditional approach)
149    Ignore,
150    /// Acknowledge both perspectives without resolution
151    Coexist,
152    /// Attempt to synthesize a higher-order understanding
153    Synthesize,
154    /// Present the paradox explicitly to the user
155    Expose,
156}
157
158/// A mission defines the overall context coordination strategy
159#[derive(Debug, Clone, Serialize, Deserialize)]
160pub struct CasialMission {
161    pub id: String,
162    pub name: String,
163    pub description: String,
164    pub templates: Vec<CasialTemplate>,
165    pub rules: Vec<CoordinationRule>,
166    pub perceptions: Vec<Perception>,
167    pub budgets: BudgetConfiguration,
168    pub created_at: DateTime<Utc>,
169    pub updated_at: DateTime<Utc>,
170}
171
172/// Budget configuration for resource management
173#[derive(Debug, Clone, Serialize, Deserialize)]
174pub struct BudgetConfiguration {
175    pub global_char_limit: Option<usize>,
176    pub per_tool_limits: AHashMap<String, usize>,
177    pub perception_quotas: AHashMap<PerceptionId, usize>,
178    pub paradox_overhead: f64, // Additional resources for paradox handling
179}
180
181/// Input for context coordination
182#[derive(Debug, Clone, Serialize, Deserialize)]
183pub struct CoordinationRequest {
184    pub tool_name: String,
185    pub tool_args: serde_json::Value,
186    pub environment: AHashMap<String, String>,
187    pub project_path: Option<String>,
188    pub active_perceptions: Vec<PerceptionId>,
189    pub paradox_tolerance: f64,
190}
191
192/// Result of context coordination
193#[derive(Debug, Clone, Serialize, Deserialize)]
194pub struct CoordinationResult {
195    pub applied: bool,
196    pub injected_content: String,
197    pub modified_args: serde_json::Value,
198    pub activated_rules: Vec<String>,
199    pub used_templates: Vec<String>,
200    pub perception_locks: Vec<PerceptionId>,
201    pub paradoxes_detected: Vec<ParadoxReport>,
202    pub metadata: AHashMap<String, serde_json::Value>,
203}
204
205/// Report of paradox detection and handling
206#[derive(Debug, Clone, Serialize, Deserialize)]
207pub struct ParadoxReport {
208    pub id: Uuid,
209    pub description: String,
210    pub conflicting_perceptions: Vec<PerceptionId>,
211    pub resolution_strategy: ParadoxStrategy,
212    pub confidence_impact: f64,
213}
214
215/// The main Casial coordination engine
216pub struct CasialEngine {
217    missions: Arc<DashMap<String, Arc<CasialMission>>>,
218    active_perceptions: Arc<DashMap<PerceptionId, Arc<RwLock<Perception>>>>,
219    coordination_history: Arc<DashMap<Uuid, CoordinationResult>>,
220    paradox_registry: Arc<DashMap<Uuid, ParadoxReport>>,
221}
222
223impl CasialEngine {
224    /// Create a new Casial engine
225    pub fn new() -> Self {
226        Self {
227            missions: Arc::new(DashMap::new()),
228            active_perceptions: Arc::new(DashMap::new()),
229            coordination_history: Arc::new(DashMap::new()),
230            paradox_registry: Arc::new(DashMap::new()),
231        }
232    }
233
234    /// Load a mission into the engine
235    pub fn load_mission(&self, mission: CasialMission) -> Result<()> {
236        let mission_id = mission.id.clone();
237        let mission_arc = Arc::new(mission);
238
239        // Register perceptions from this mission
240        for perception in &mission_arc.perceptions {
241            self.active_perceptions
242                .insert(perception.id, Arc::new(RwLock::new(perception.clone())));
243        }
244
245        self.missions.insert(mission_id, mission_arc);
246        Ok(())
247    }
248
249    /// Coordinate context for a tool request
250    pub fn coordinate(&self, request: CoordinationRequest) -> Result<CoordinationResult> {
251        // Find applicable missions (could be multiple for different perceptions)
252        let applicable_missions: Vec<Arc<CasialMission>> = self
253            .missions
254            .iter()
255            .map(|entry| entry.value().clone())
256            .collect();
257
258        if applicable_missions.is_empty() {
259            return Ok(CoordinationResult {
260                applied: false,
261                injected_content: String::new(),
262                modified_args: request.tool_args,
263                activated_rules: vec![],
264                used_templates: vec![],
265                perception_locks: vec![],
266                paradoxes_detected: vec![],
267                metadata: AHashMap::new(),
268            });
269        }
270
271        // Evaluate rules across all applicable missions
272        let mut activated_rules = Vec::new();
273        let mut applicable_templates = AHashMap::new();
274        let mut detected_paradoxes = Vec::new();
275
276        for mission in &applicable_missions {
277            for rule in &mission.rules {
278                if !rule.enabled {
279                    continue;
280                }
281
282                if self.evaluate_rule_conditions(&rule.conditions, &request)? {
283                    activated_rules.push(rule.id.clone());
284
285                    // Collect templates from this rule
286                    for template_id in &rule.actions.template_ids {
287                        if let Some(template) =
288                            mission.templates.iter().find(|t| t.id == *template_id)
289                        {
290                            // Check for perception conflicts (paradoxes)
291                            if let Some(existing) = applicable_templates.get(template_id) {
292                                let existing_template: &CasialTemplate = existing;
293                                if !existing_template.perception_affinity.is_empty()
294                                    && !template.perception_affinity.is_empty()
295                                    && existing_template.perception_affinity
296                                        != template.perception_affinity
297                                {
298                                    // Paradox detected!
299                                    let paradox = ParadoxReport {
300                                        id: Uuid::new_v4(),
301                                        description: format!(
302                                            "Template '{}' has conflicting perception affinities",
303                                            template_id
304                                        ),
305                                        conflicting_perceptions: [
306                                            existing_template.perception_affinity.clone(),
307                                            template.perception_affinity.clone(),
308                                        ]
309                                        .concat(),
310                                        resolution_strategy: rule.paradox_handling.clone(),
311                                        confidence_impact: 1.0 - template.paradox_resistance,
312                                    };
313
314                                    detected_paradoxes.push(paradox.clone());
315                                    self.paradox_registry.insert(paradox.id, paradox);
316                                }
317                            }
318
319                            applicable_templates.insert(template_id.clone(), template.clone());
320                        }
321                    }
322                }
323            }
324        }
325
326        // Apply paradox handling strategies
327        let resolved_templates = self.resolve_paradoxes(
328            applicable_templates,
329            &detected_paradoxes,
330            request.paradox_tolerance,
331        )?;
332
333        // Compose final content
334        let (injected_content, used_templates) =
335            self.compose_context(resolved_templates, &applicable_missions[0].budgets)?;
336
337        // Apply transformations
338        let modified_args = self.apply_transformation(
339            &request.tool_args,
340            &injected_content,
341            &activated_rules,
342            &applicable_missions,
343        )?;
344
345        let result = CoordinationResult {
346            applied: !used_templates.is_empty(),
347            injected_content,
348            modified_args,
349            activated_rules,
350            used_templates,
351            perception_locks: request.active_perceptions.clone(),
352            paradoxes_detected: detected_paradoxes,
353            metadata: self.generate_metadata(&request)?,
354        };
355
356        // Store in history
357        let history_id = Uuid::new_v4();
358        self.coordination_history.insert(history_id, result.clone());
359
360        Ok(result)
361    }
362
363    /// Evaluate if rule conditions are met
364    fn evaluate_rule_conditions(
365        &self,
366        conditions: &RuleConditions,
367        request: &CoordinationRequest,
368    ) -> Result<bool> {
369        // Tool pattern matching
370        if !conditions.tool_patterns.is_empty() {
371            let matches = conditions
372                .tool_patterns
373                .iter()
374                .any(|pattern| request.tool_name.contains(pattern));
375            if !matches {
376                return Ok(false);
377            }
378        }
379
380        // Environment variable matching
381        for (key, expected) in &conditions.environment_vars {
382            if let Some(actual) = request.environment.get(key) {
383                if !actual.contains(expected) {
384                    return Ok(false);
385                }
386            } else {
387                return Ok(false);
388            }
389        }
390
391        // File signal evaluation
392        if let Some(project_path) = &request.project_path {
393            for signal in &conditions.file_signals {
394                if !self.evaluate_file_signal(signal, project_path)? {
395                    return Ok(false);
396                }
397            }
398        }
399
400        // Perception state matching
401        if !conditions.perception_states.is_empty() {
402            let has_required_perception = conditions
403                .perception_states
404                .iter()
405                .any(|required| request.active_perceptions.contains(required));
406            if !has_required_perception {
407                return Ok(false);
408            }
409        }
410
411        Ok(true)
412    }
413
414    /// Evaluate a file signal condition
415    fn evaluate_file_signal(&self, signal: &FileSignal, project_path: &str) -> Result<bool> {
416        let file_path = std::path::Path::new(project_path).join(&signal.path);
417
418        let exists = file_path.exists();
419        if signal.must_exist && !exists {
420            return Ok(false);
421        }
422
423        if exists {
424            if let Some(expected_content) = &signal.contains {
425                let content = std::fs::read_to_string(&file_path)
426                    .context("Failed to read file for signal evaluation")?;
427                if !content.contains(expected_content) {
428                    return Ok(false);
429                }
430            }
431
432            if let Some(modified_since) = signal.modified_since {
433                let metadata =
434                    std::fs::metadata(&file_path).context("Failed to read file metadata")?;
435                let modified = metadata
436                    .modified()
437                    .context("Failed to get file modification time")?;
438                let modified_dt = DateTime::<Utc>::from(modified);
439                if modified_dt < modified_since {
440                    return Ok(false);
441                }
442            }
443        }
444
445        Ok(true)
446    }
447
448    /// Resolve paradoxes using various strategies
449    fn resolve_paradoxes(
450        &self,
451        templates: AHashMap<String, CasialTemplate>,
452        paradoxes: &[ParadoxReport],
453        tolerance: f64,
454    ) -> Result<Vec<CasialTemplate>> {
455        let mut resolved = Vec::new();
456        let mut processed_ids = std::collections::HashSet::new();
457
458        for template in templates.into_values() {
459            if processed_ids.contains(&template.id) {
460                continue;
461            }
462
463            // Check if this template is involved in any paradoxes
464            let involved_paradoxes: Vec<&ParadoxReport> = paradoxes
465                .iter()
466                .filter(|p| p.confidence_impact > tolerance)
467                .collect();
468
469            if involved_paradoxes.is_empty() || template.paradox_resistance >= tolerance {
470                // No significant paradoxes or template is resistant enough
471                processed_ids.insert(template.id.clone());
472                resolved.push(template);
473            } else {
474                // Handle paradox based on strategy
475                let mut should_include = false;
476                for paradox in &involved_paradoxes {
477                    match paradox.resolution_strategy {
478                        ParadoxStrategy::Ignore => {
479                            should_include = true;
480                        }
481                        ParadoxStrategy::Coexist => {
482                            // Include template but mark the paradox
483                            should_include = true;
484                        }
485                        ParadoxStrategy::Synthesize => {
486                            // For now, include the most resistant template
487                            if template.paradox_resistance >= 0.5 {
488                                should_include = true;
489                            }
490                        }
491                        ParadoxStrategy::Expose => {
492                            // Include template and let the paradox be visible
493                            should_include = true;
494                        }
495                    }
496                }
497                processed_ids.insert(template.id.clone());
498                if should_include {
499                    resolved.push(template);
500                }
501            }
502        }
503
504        Ok(resolved)
505    }
506
507    /// Compose context from resolved templates
508    fn compose_context(
509        &self,
510        templates: Vec<CasialTemplate>,
511        budget: &BudgetConfiguration,
512    ) -> Result<(String, Vec<String>)> {
513        let mut sorted_templates = templates;
514        sorted_templates.sort_by_key(|t| t.priority);
515
516        let mut content = String::new();
517        let mut used_templates = Vec::new();
518        let mut char_count = 0;
519
520        let char_limit = budget.global_char_limit.unwrap_or(usize::MAX);
521        let paradox_overhead = (char_limit as f64 * budget.paradox_overhead) as usize;
522        let effective_limit = char_limit.saturating_sub(paradox_overhead);
523
524        for template in sorted_templates {
525            if !template.enabled {
526                continue;
527            }
528
529            let template_content = format!("## {}\n\n{}\n\n", template.name, template.content);
530
531            if char_count + template_content.len() > effective_limit {
532                break;
533            }
534
535            content.push_str(&template_content);
536            char_count += template_content.len();
537            used_templates.push(template.id.clone());
538        }
539
540        Ok((content, used_templates))
541    }
542
543    /// Apply transformations to the tool arguments
544    fn apply_transformation(
545        &self,
546        args: &serde_json::Value,
547        content: &str,
548        _rules: &[String],
549        missions: &[Arc<CasialMission>],
550    ) -> Result<serde_json::Value> {
551        let mut modified_args = args.clone();
552
553        // Find the primary transformation type (from the first applicable rule)
554        let transform_type = missions
555            .iter()
556            .flat_map(|m| &m.rules)
557            .find(|r| r.enabled)
558            .map(|r| &r.actions.transform_type)
559            .unwrap_or(&TransformType::Prepend);
560
561        match transform_type {
562            TransformType::Prepend => {
563                if let Some(query) = modified_args.get_mut("query") {
564                    if let Some(query_str) = query.as_str() {
565                        *query = serde_json::Value::String(format!("{}\n\n{}", content, query_str));
566                    }
567                } else if let Some(instructions) = modified_args.get_mut("instructions") {
568                    if let Some(instr_str) = instructions.as_str() {
569                        *instructions =
570                            serde_json::Value::String(format!("{}\n\n{}", content, instr_str));
571                    }
572                }
573            }
574            TransformType::Append => {
575                if let Some(query) = modified_args.get_mut("query") {
576                    if let Some(query_str) = query.as_str() {
577                        *query = serde_json::Value::String(format!("{}\n\n{}", query_str, content));
578                    }
579                }
580            }
581            TransformType::InjectField => {
582                if let Some(obj) = modified_args.as_object_mut() {
583                    obj.insert(
584                        "casial_context".to_string(),
585                        serde_json::Value::String(content.to_string()),
586                    );
587                }
588            }
589            TransformType::SystemInstruction => {
590                if let Some(obj) = modified_args.as_object_mut() {
591                    obj.insert(
592                        "system_context".to_string(),
593                        serde_json::Value::String(content.to_string()),
594                    );
595                }
596            }
597            TransformType::PerceptionLayer => {
598                if let Some(obj) = modified_args.as_object_mut() {
599                    obj.insert(
600                        "perception_context".to_string(),
601                        serde_json::Value::String(content.to_string()),
602                    );
603                }
604            }
605        }
606
607        Ok(modified_args)
608    }
609
610    /// Generate metadata for the coordination result
611    fn generate_metadata(
612        &self,
613        request: &CoordinationRequest,
614    ) -> Result<AHashMap<String, serde_json::Value>> {
615        let mut metadata = AHashMap::new();
616
617        metadata.insert(
618            "timestamp".to_string(),
619            serde_json::Value::String(Utc::now().to_rfc3339()),
620        );
621        metadata.insert(
622            "tool_name".to_string(),
623            serde_json::Value::String(request.tool_name.clone()),
624        );
625        metadata.insert(
626            "perception_count".to_string(),
627            serde_json::Value::Number(serde_json::Number::from(request.active_perceptions.len())),
628        );
629        metadata.insert(
630            "paradox_tolerance".to_string(),
631            serde_json::Value::Number(
632                serde_json::Number::from_f64(request.paradox_tolerance).unwrap(),
633            ),
634        );
635
636        Ok(metadata)
637    }
638
639    /// Get coordination history for analysis
640    pub fn get_coordination_history(&self) -> Vec<CoordinationResult> {
641        self.coordination_history
642            .iter()
643            .map(|entry| entry.value().clone())
644            .collect()
645    }
646
647    /// Get paradox registry for analysis
648    pub fn get_paradox_registry(&self) -> Vec<ParadoxReport> {
649        self.paradox_registry
650            .iter()
651            .map(|entry| entry.value().clone())
652            .collect()
653    }
654}
655
656impl Default for CasialEngine {
657    fn default() -> Self {
658        Self::new()
659    }
660}
661
662#[cfg(test)]
663mod tests {
664    use super::*;
665
666    #[test]
667    fn test_casial_engine_creation() {
668        let engine = CasialEngine::new();
669        assert_eq!(engine.missions.len(), 0);
670        assert_eq!(engine.active_perceptions.len(), 0);
671    }
672
673    #[test]
674    fn test_perception_id_generation() {
675        let id1 = PerceptionId::new();
676        let id2 = PerceptionId::new();
677        assert_ne!(id1, id2);
678    }
679}