pe-core 0.1.0

Core types for Potential Expectations — messages, channels, state, traits
Documentation
//! Cognitive signals — inner world → outer world communication.
//!
//! When the cognitive graph processes input, lobes may emit signals
//! that communicate to the outer execution graph. The outer graph
//! decides how to handle each signal — the library provides the
//! vocabulary, users define the policy.

use serde::{Deserialize, Serialize};

/// A signal emitted by the cognitive graph to the outer execution.
///
/// Signals are the inner world's way of influencing outer behavior
/// without directly modifying the outer graph's state. The outer
/// graph reads signals after cognitive processing completes and
/// decides how to respond.
///
/// # Example
///
/// ```
/// use pe_core::cognitive_signal::CognitiveSignal;
///
/// let signal = CognitiveSignal::ProceedWithCaution {
///     concern: "Low confidence in API response format".into(),
/// };
/// assert!(signal.is_cautionary());
/// ```
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[non_exhaustive]
pub enum CognitiveSignal {
    /// "I'm confident, proceed normally."
    Proceed,

    /// "I need more information before I can answer."
    NeedMoreContext { description: String },

    /// "The current approach is wrong, try a different path."
    ChangeStrategy { suggestion: String },

    /// "I'm stuck, escalate to human."
    Escalate { reason: String },

    /// "I can answer but I'm not sure — flag for review."
    ProceedWithCaution { concern: String },

    /// "Diminishing returns. Stop iterating."
    StopEarly { reason: String },

    /// "Budget is low. Reduce cognitive depth."
    SimplifyMode,

    /// "I need a capability I don't have."
    RequestCapability { capability: String },

    /// "Block this output — safety or quality concern."
    Veto { lobe: String, reason: String },

    /// User-defined signal for domain-specific communication.
    Custom {
        name: String,
        data: serde_json::Value,
    },
}

impl CognitiveSignal {
    /// Whether this signal blocks execution (veto, escalate).
    pub fn is_blocking(&self) -> bool {
        matches!(self, Self::Veto { .. } | Self::Escalate { .. })
    }

    /// Whether this signal suggests caution but doesn't block.
    pub fn is_cautionary(&self) -> bool {
        matches!(
            self,
            Self::ProceedWithCaution { .. }
                | Self::NeedMoreContext { .. }
                | Self::ChangeStrategy { .. }
        )
    }

    /// Whether this signal suggests reducing work.
    pub fn is_simplifying(&self) -> bool {
        matches!(self, Self::SimplifyMode | Self::StopEarly { .. })
    }

    /// Human-readable summary for logging/debugging.
    pub fn summary(&self) -> String {
        match self {
            Self::Proceed => "proceed".into(),
            Self::NeedMoreContext { description } => format!("need context: {description}"),
            Self::ChangeStrategy { suggestion } => format!("change strategy: {suggestion}"),
            Self::Escalate { reason } => format!("escalate: {reason}"),
            Self::ProceedWithCaution { concern } => format!("caution: {concern}"),
            Self::StopEarly { reason } => format!("stop early: {reason}"),
            Self::SimplifyMode => "simplify mode".into(),
            Self::RequestCapability { capability } => format!("request: {capability}"),
            Self::Veto { lobe, reason } => format!("VETO by {lobe}: {reason}"),
            Self::Custom { name, .. } => format!("custom: {name}"),
        }
    }
}

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

    #[test]
    fn test_blocking_signals() {
        assert!(
            CognitiveSignal::Veto {
                lobe: "safety".into(),
                reason: "dangerous".into()
            }
            .is_blocking()
        );
        assert!(
            CognitiveSignal::Escalate {
                reason: "stuck".into()
            }
            .is_blocking()
        );
        assert!(!CognitiveSignal::Proceed.is_blocking());
    }

    #[test]
    fn test_cautionary_signals() {
        assert!(
            CognitiveSignal::ProceedWithCaution {
                concern: "low confidence".into()
            }
            .is_cautionary()
        );
        assert!(!CognitiveSignal::Proceed.is_cautionary());
        assert!(!CognitiveSignal::SimplifyMode.is_cautionary());
    }

    #[test]
    fn test_simplifying_signals() {
        assert!(CognitiveSignal::SimplifyMode.is_simplifying());
        assert!(
            CognitiveSignal::StopEarly {
                reason: "done".into()
            }
            .is_simplifying()
        );
        assert!(!CognitiveSignal::Proceed.is_simplifying());
    }

    #[test]
    fn test_serialization_roundtrip() {
        let signals = vec![
            CognitiveSignal::Proceed,
            CognitiveSignal::Veto {
                lobe: "critic".into(),
                reason: "unsafe output".into(),
            },
            CognitiveSignal::Custom {
                name: "domain_check".into(),
                data: serde_json::json!({"score": 0.95}),
            },
        ];
        for signal in &signals {
            let json = serde_json::to_string(signal).unwrap();
            let back: CognitiveSignal = serde_json::from_str(&json).unwrap();
            assert_eq!(&back, signal);
        }
    }

    #[test]
    fn test_summary_formatting() {
        assert_eq!(CognitiveSignal::Proceed.summary(), "proceed");
        let veto = CognitiveSignal::Veto {
            lobe: "safety".into(),
            reason: "PII detected".into(),
        };
        assert!(veto.summary().contains("VETO"));
        assert!(veto.summary().contains("PII detected"));
    }
}