use crate::cognitive_budget::CognitiveBudget;
use crate::cognitive_memory::{MemoryConfig, WorkingNote};
use crate::cognitive_signal::CognitiveSignal;
use crate::lobe::LobeOutput;
use crate::self_model::{FailureRecord, NegativeKnowledge, SelfModel};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CognitiveArchitecture {
pub streams: Vec<CognitiveStream>,
pub synthesis: SynthesisStrategy,
#[serde(default)]
pub budget: CognitiveBudget,
#[serde(default)]
pub memory_config: MemoryConfig,
#[serde(default)]
pub self_model: SelfModel,
}
impl CognitiveArchitecture {
pub fn new() -> Self {
Self {
streams: Vec::new(),
synthesis: SynthesisStrategy::Integrate,
budget: CognitiveBudget::default(),
memory_config: MemoryConfig::default(),
self_model: SelfModel::default(),
}
}
#[must_use]
pub fn add_stream(mut self, stream: CognitiveStream) -> Self {
self.streams.push(stream);
self
}
#[must_use]
pub fn with_synthesis(mut self, strategy: SynthesisStrategy) -> Self {
self.synthesis = strategy;
self
}
#[must_use]
pub fn with_budget(mut self, budget: CognitiveBudget) -> Self {
self.budget = budget;
self
}
#[must_use]
pub fn with_memory_config(mut self, config: MemoryConfig) -> Self {
self.memory_config = config;
self
}
#[must_use]
pub fn with_self_model(mut self, model: SelfModel) -> Self {
self.self_model = model;
self
}
}
impl Default for CognitiveArchitecture {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CognitiveStream {
pub lens: String,
pub prompt_modifier: String,
#[serde(default)]
pub model_override: Option<String>,
#[serde(default)]
pub metadata: HashMap<String, serde_json::Value>,
}
impl CognitiveStream {
pub fn new(lens: impl Into<String>, prompt_modifier: impl Into<String>) -> Self {
Self {
lens: lens.into(),
prompt_modifier: prompt_modifier.into(),
model_override: None,
metadata: HashMap::new(),
}
}
#[must_use]
pub fn with_model(mut self, model: impl Into<String>) -> Self {
self.model_override = Some(model.into());
self
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
#[non_exhaustive]
pub enum SynthesisStrategy {
#[default]
Integrate,
Vote,
Weighted,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct CognitiveState {
pub input: String,
#[serde(default)]
pub stream_outputs: HashMap<String, LobeOutput>,
#[serde(default)]
pub synthesis_result: Option<String>,
#[serde(default)]
pub budget_tokens: Option<u32>,
#[serde(default)]
pub loaded_memories: Vec<String>,
#[serde(default)]
pub working_notes: Vec<WorkingNote>,
#[serde(default)]
pub current_plan: Option<String>,
#[serde(default)]
pub confidence: f64,
#[serde(default)]
pub quality_trend: Vec<f64>,
#[serde(default)]
pub error_history: Vec<String>,
#[serde(default)]
pub signals: Vec<CognitiveSignal>,
#[serde(default)]
pub negative_knowledge: Vec<NegativeKnowledge>,
#[serde(default)]
pub failure_records: Vec<FailureRecord>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct CognitiveStateUpdate {
#[serde(default)]
pub input: Option<String>,
#[serde(default)]
pub stream_outputs: Option<HashMap<String, LobeOutput>>,
#[serde(default)]
pub synthesis_result: Option<Option<String>>,
#[serde(default)]
pub budget_tokens: Option<Option<u32>>,
#[serde(default)]
pub loaded_memories: Option<Vec<String>>,
#[serde(default)]
pub working_notes: Option<Vec<WorkingNote>>,
#[serde(default)]
pub current_plan: Option<Option<String>>,
#[serde(default)]
pub confidence: Option<f64>,
#[serde(default)]
pub quality_trend: Option<Vec<f64>>,
#[serde(default)]
pub error_history: Option<Vec<String>>,
#[serde(default)]
pub signals: Option<Vec<CognitiveSignal>>,
#[serde(default)]
pub negative_knowledge: Option<Vec<NegativeKnowledge>>,
#[serde(default)]
pub failure_records: Option<Vec<FailureRecord>>,
#[serde(default)]
pub replace_working_notes: Option<Vec<WorkingNote>>,
#[serde(default)]
pub replace_failure_records: Option<Vec<FailureRecord>>,
#[serde(default)]
pub replace_negative_knowledge: Option<Vec<NegativeKnowledge>>,
}
impl crate::state::StateUpdate for CognitiveStateUpdate {}
impl crate::state::State for CognitiveState {
type Update = CognitiveStateUpdate;
fn apply(&mut self, update: CognitiveStateUpdate) {
if let Some(input) = update.input {
self.input = input;
}
if let Some(synthesis) = update.synthesis_result {
self.synthesis_result = synthesis;
}
if let Some(budget) = update.budget_tokens {
self.budget_tokens = budget;
}
if let Some(plan) = update.current_plan {
self.current_plan = plan;
}
if let Some(confidence) = update.confidence {
self.confidence = confidence;
}
if let Some(outputs) = update.stream_outputs {
self.stream_outputs.extend(outputs);
}
if let Some(notes) = update.replace_working_notes {
self.working_notes = notes;
} else if let Some(notes) = update.working_notes {
self.working_notes.extend(notes);
}
if let Some(failures) = update.replace_failure_records {
self.failure_records = failures;
} else if let Some(failures) = update.failure_records {
self.failure_records.extend(failures);
}
if let Some(nk) = update.replace_negative_knowledge {
self.negative_knowledge = nk;
} else if let Some(nk) = update.negative_knowledge {
self.negative_knowledge.extend(nk);
}
if let Some(memories) = update.loaded_memories {
self.loaded_memories.extend(memories);
}
if let Some(scores) = update.quality_trend {
self.quality_trend.extend(scores);
}
if let Some(errors) = update.error_history {
self.error_history.extend(errors);
}
if let Some(signals) = update.signals {
self.signals.extend(signals);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::cognitive_memory::NoteCategory;
use crate::self_model::{FailureRecord, NegativeKnowledge};
use crate::state::State;
fn base_state() -> CognitiveState {
CognitiveState {
input: "analyze this".to_string(),
stream_outputs: HashMap::from([(
"logical".to_string(),
LobeOutput::new("logic output", 0.9).with_lobe_name("logical"),
)]),
synthesis_result: None,
budget_tokens: Some(1000),
loaded_memories: vec!["mem-1".to_string()],
working_notes: Vec::new(),
current_plan: None,
confidence: 0.5,
quality_trend: Vec::new(),
error_history: Vec::new(),
signals: Vec::new(),
negative_knowledge: Vec::new(),
failure_records: Vec::new(),
}
}
#[test]
fn test_apply_input_replaces() {
let mut state = base_state();
state.apply(CognitiveStateUpdate {
input: Some("new input".to_string()),
..Default::default()
});
assert_eq!(state.input, "new input");
assert_eq!(state.stream_outputs.len(), 1);
assert_eq!(state.budget_tokens, Some(1000));
}
#[test]
fn test_apply_stream_outputs_merges() {
let mut state = base_state();
state.apply(CognitiveStateUpdate {
stream_outputs: Some(HashMap::from([
(
"emotional".to_string(),
LobeOutput::new("emotion output", 0.7).with_lobe_name("emotional"),
),
(
"logical".to_string(),
LobeOutput::new("updated logic", 0.95).with_lobe_name("logical"),
),
])),
..Default::default()
});
assert_eq!(
state.stream_outputs.get("emotional").unwrap().content,
"emotion output"
);
assert_eq!(
state.stream_outputs.get("logical").unwrap().content,
"updated logic"
);
assert!(
(state.stream_outputs.get("logical").unwrap().confidence - 0.95).abs() < f64::EPSILON
);
assert_eq!(state.stream_outputs.len(), 2);
}
#[test]
fn test_apply_synthesis_result_replaces() {
let mut state = base_state();
assert!(state.synthesis_result.is_none());
state.apply(CognitiveStateUpdate {
synthesis_result: Some(Some("synthesized answer".to_string())),
..Default::default()
});
assert_eq!(
state.synthesis_result.as_deref(),
Some("synthesized answer")
);
state.apply(CognitiveStateUpdate {
synthesis_result: Some(None),
..Default::default()
});
assert!(state.synthesis_result.is_none());
}
#[test]
fn test_apply_loaded_memories_appends() {
let mut state = base_state();
assert_eq!(state.loaded_memories, vec!["mem-1"]);
state.apply(CognitiveStateUpdate {
loaded_memories: Some(vec!["mem-2".to_string(), "mem-3".to_string()]),
..Default::default()
});
assert_eq!(state.loaded_memories, vec!["mem-1", "mem-2", "mem-3"]);
}
#[test]
fn test_apply_none_fields_no_change() {
let mut state = base_state();
let original = state.clone();
state.apply(CognitiveStateUpdate::default());
assert_eq!(state.input, original.input);
assert_eq!(state.stream_outputs, original.stream_outputs);
assert_eq!(state.synthesis_result, original.synthesis_result);
assert_eq!(state.budget_tokens, original.budget_tokens);
assert_eq!(state.loaded_memories, original.loaded_memories);
}
#[test]
fn test_apply_multiple_fields_at_once() {
let mut state = base_state();
state.apply(CognitiveStateUpdate {
input: Some("new prompt".to_string()),
stream_outputs: Some(HashMap::from([(
"creative".to_string(),
LobeOutput::new("creative out", 0.8).with_lobe_name("creative"),
)])),
synthesis_result: Some(Some("final".to_string())),
budget_tokens: Some(Some(500)),
loaded_memories: Some(vec!["mem-4".to_string()]),
..Default::default()
});
assert_eq!(state.input, "new prompt");
assert_eq!(state.stream_outputs.len(), 2); assert_eq!(state.synthesis_result.as_deref(), Some("final"));
assert_eq!(state.budget_tokens, Some(500));
assert_eq!(state.loaded_memories, vec!["mem-1", "mem-4"]);
}
#[test]
fn test_apply_working_notes_appends() {
let mut state = base_state();
let note = WorkingNote::new("important finding", NoteCategory::Discovery);
state.apply(CognitiveStateUpdate {
working_notes: Some(vec![note]),
..Default::default()
});
assert_eq!(state.working_notes.len(), 1);
assert_eq!(state.working_notes[0].content, "important finding");
state.apply(CognitiveStateUpdate {
working_notes: Some(vec![WorkingNote::new("concern", NoteCategory::Concern)]),
..Default::default()
});
assert_eq!(state.working_notes.len(), 2);
}
#[test]
fn test_apply_confidence_replaces() {
let mut state = base_state();
assert!((state.confidence - 0.5).abs() < f64::EPSILON);
state.apply(CognitiveStateUpdate {
confidence: Some(0.9),
..Default::default()
});
assert!((state.confidence - 0.9).abs() < f64::EPSILON);
}
#[test]
fn test_apply_signals_appends() {
let mut state = base_state();
state.apply(CognitiveStateUpdate {
signals: Some(vec![CognitiveSignal::Proceed]),
..Default::default()
});
state.apply(CognitiveStateUpdate {
signals: Some(vec![CognitiveSignal::SimplifyMode]),
..Default::default()
});
assert_eq!(state.signals.len(), 2);
}
#[test]
fn test_apply_negative_knowledge_appends() {
use crate::self_model::Severity;
let mut state = base_state();
let nk = NegativeKnowledge::new("api", "max 100 items", Severity::High);
state.apply(CognitiveStateUpdate {
negative_knowledge: Some(vec![nk]),
..Default::default()
});
assert_eq!(state.negative_knowledge.len(), 1);
assert_eq!(state.negative_knowledge[0].category, "api");
}
#[test]
fn test_apply_failure_records_appends() {
let mut state = base_state();
let record = FailureRecord::new("db_migration", "ALTER TABLE");
state.apply(CognitiveStateUpdate {
failure_records: Some(vec![record]),
..Default::default()
});
assert_eq!(state.failure_records.len(), 1);
}
#[test]
fn test_cognitive_state_update_serialization() {
let update = CognitiveStateUpdate {
input: Some("test".to_string()),
..Default::default()
};
let json = serde_json::to_string(&update).unwrap();
let deserialized: CognitiveStateUpdate = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized.input.as_deref(), Some("test"));
assert!(deserialized.stream_outputs.is_none());
}
#[test]
fn test_replace_working_notes_overrides_existing() {
let mut state = base_state();
state.apply(CognitiveStateUpdate {
working_notes: Some(vec![
WorkingNote::new("old note 1", NoteCategory::Observation),
WorkingNote::new("old note 2", NoteCategory::Concern),
WorkingNote::new("old note 3", NoteCategory::Discovery),
]),
..Default::default()
});
assert_eq!(state.working_notes.len(), 3);
state.apply(CognitiveStateUpdate {
replace_working_notes: Some(vec![WorkingNote::new(
"consolidated",
NoteCategory::Reflection,
)]),
..Default::default()
});
assert_eq!(state.working_notes.len(), 1);
assert_eq!(state.working_notes[0].content, "consolidated");
}
#[test]
fn test_replace_takes_precedence_over_append() {
let mut state = base_state();
state.apply(CognitiveStateUpdate {
working_notes: Some(vec![WorkingNote::new("old", NoteCategory::Observation)]),
..Default::default()
});
assert_eq!(state.working_notes.len(), 1);
state.apply(CognitiveStateUpdate {
working_notes: Some(vec![WorkingNote::new("appended", NoteCategory::Concern)]),
replace_working_notes: Some(vec![WorkingNote::new("replaced", NoteCategory::Plan)]),
..Default::default()
});
assert_eq!(state.working_notes.len(), 1);
assert_eq!(state.working_notes[0].content, "replaced");
}
}