1use crate::cognitive_budget::CognitiveBudget;
30use crate::cognitive_memory::{MemoryConfig, WorkingNote};
31use crate::cognitive_signal::CognitiveSignal;
32use crate::lobe::LobeOutput;
33use crate::self_model::{FailureRecord, NegativeKnowledge, SelfModel};
34use serde::{Deserialize, Serialize};
35use std::collections::HashMap;
36
37#[derive(Debug, Clone, Serialize, Deserialize)]
55pub struct CognitiveArchitecture {
56 pub streams: Vec<CognitiveStream>,
58
59 pub synthesis: SynthesisStrategy,
61
62 #[serde(default)]
64 pub budget: CognitiveBudget,
65
66 #[serde(default)]
68 pub memory_config: MemoryConfig,
69
70 #[serde(default)]
72 pub self_model: SelfModel,
73}
74
75impl CognitiveArchitecture {
76 pub fn new() -> Self {
78 Self {
79 streams: Vec::new(),
80 synthesis: SynthesisStrategy::Integrate,
81 budget: CognitiveBudget::default(),
82 memory_config: MemoryConfig::default(),
83 self_model: SelfModel::default(),
84 }
85 }
86
87 #[must_use]
89 pub fn add_stream(mut self, stream: CognitiveStream) -> Self {
90 self.streams.push(stream);
91 self
92 }
93
94 #[must_use]
96 pub fn with_synthesis(mut self, strategy: SynthesisStrategy) -> Self {
97 self.synthesis = strategy;
98 self
99 }
100
101 #[must_use]
103 pub fn with_budget(mut self, budget: CognitiveBudget) -> Self {
104 self.budget = budget;
105 self
106 }
107
108 #[must_use]
110 pub fn with_memory_config(mut self, config: MemoryConfig) -> Self {
111 self.memory_config = config;
112 self
113 }
114
115 #[must_use]
117 pub fn with_self_model(mut self, model: SelfModel) -> Self {
118 self.self_model = model;
119 self
120 }
121}
122
123impl Default for CognitiveArchitecture {
124 fn default() -> Self {
125 Self::new()
126 }
127}
128
129#[derive(Debug, Clone, Serialize, Deserialize)]
135pub struct CognitiveStream {
136 pub lens: String,
139
140 pub prompt_modifier: String,
143
144 #[serde(default)]
148 pub model_override: Option<String>,
149
150 #[serde(default)]
152 pub metadata: HashMap<String, serde_json::Value>,
153}
154
155impl CognitiveStream {
156 pub fn new(lens: impl Into<String>, prompt_modifier: impl Into<String>) -> Self {
158 Self {
159 lens: lens.into(),
160 prompt_modifier: prompt_modifier.into(),
161 model_override: None,
162 metadata: HashMap::new(),
163 }
164 }
165
166 #[must_use]
168 pub fn with_model(mut self, model: impl Into<String>) -> Self {
169 self.model_override = Some(model.into());
170 self
171 }
172}
173
174#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
176#[non_exhaustive]
177pub enum SynthesisStrategy {
178 #[default]
181 Integrate,
182
183 Vote,
185
186 Weighted,
188}
189
190#[derive(Debug, Clone, Default, Serialize, Deserialize)]
209pub struct CognitiveState {
210 pub input: String,
212
213 #[serde(default)]
218 pub stream_outputs: HashMap<String, LobeOutput>,
219
220 #[serde(default)]
222 pub synthesis_result: Option<String>,
223
224 #[serde(default)]
226 pub budget_tokens: Option<u32>,
227
228 #[serde(default)]
230 pub loaded_memories: Vec<String>,
231
232 #[serde(default)]
235 pub working_notes: Vec<WorkingNote>,
236
237 #[serde(default)]
239 pub current_plan: Option<String>,
240
241 #[serde(default)]
244 pub confidence: f64,
245
246 #[serde(default)]
248 pub quality_trend: Vec<f64>,
249
250 #[serde(default)]
252 pub error_history: Vec<String>,
253
254 #[serde(default)]
257 pub signals: Vec<CognitiveSignal>,
258
259 #[serde(default)]
262 pub negative_knowledge: Vec<NegativeKnowledge>,
263
264 #[serde(default)]
266 pub failure_records: Vec<FailureRecord>,
267}
268
269#[derive(Debug, Clone, Default, Serialize, Deserialize)]
276pub struct CognitiveStateUpdate {
277 #[serde(default)]
279 pub input: Option<String>,
280
281 #[serde(default)]
283 pub stream_outputs: Option<HashMap<String, LobeOutput>>,
284
285 #[serde(default)]
287 pub synthesis_result: Option<Option<String>>,
288
289 #[serde(default)]
291 pub budget_tokens: Option<Option<u32>>,
292
293 #[serde(default)]
295 pub loaded_memories: Option<Vec<String>>,
296
297 #[serde(default)]
299 pub working_notes: Option<Vec<WorkingNote>>,
300
301 #[serde(default)]
303 pub current_plan: Option<Option<String>>,
304
305 #[serde(default)]
307 pub confidence: Option<f64>,
308
309 #[serde(default)]
311 pub quality_trend: Option<Vec<f64>>,
312
313 #[serde(default)]
315 pub error_history: Option<Vec<String>>,
316
317 #[serde(default)]
319 pub signals: Option<Vec<CognitiveSignal>>,
320
321 #[serde(default)]
323 pub negative_knowledge: Option<Vec<NegativeKnowledge>>,
324
325 #[serde(default)]
327 pub failure_records: Option<Vec<FailureRecord>>,
328
329 #[serde(default)]
334 pub replace_working_notes: Option<Vec<WorkingNote>>,
335
336 #[serde(default)]
341 pub replace_failure_records: Option<Vec<FailureRecord>>,
342
343 #[serde(default)]
348 pub replace_negative_knowledge: Option<Vec<NegativeKnowledge>>,
349}
350
351impl crate::state::StateUpdate for CognitiveStateUpdate {}
352
353impl crate::state::State for CognitiveState {
354 type Update = CognitiveStateUpdate;
355
356 fn apply(&mut self, update: CognitiveStateUpdate) {
357 if let Some(input) = update.input {
359 self.input = input;
360 }
361 if let Some(synthesis) = update.synthesis_result {
362 self.synthesis_result = synthesis;
363 }
364 if let Some(budget) = update.budget_tokens {
365 self.budget_tokens = budget;
366 }
367 if let Some(plan) = update.current_plan {
368 self.current_plan = plan;
369 }
370 if let Some(confidence) = update.confidence {
371 self.confidence = confidence;
372 }
373
374 if let Some(outputs) = update.stream_outputs {
376 self.stream_outputs.extend(outputs);
377 }
378
379 if let Some(notes) = update.replace_working_notes {
381 self.working_notes = notes;
382 } else if let Some(notes) = update.working_notes {
383 self.working_notes.extend(notes);
384 }
385 if let Some(failures) = update.replace_failure_records {
386 self.failure_records = failures;
387 } else if let Some(failures) = update.failure_records {
388 self.failure_records.extend(failures);
389 }
390 if let Some(nk) = update.replace_negative_knowledge {
391 self.negative_knowledge = nk;
392 } else if let Some(nk) = update.negative_knowledge {
393 self.negative_knowledge.extend(nk);
394 }
395
396 if let Some(memories) = update.loaded_memories {
398 self.loaded_memories.extend(memories);
399 }
400 if let Some(scores) = update.quality_trend {
401 self.quality_trend.extend(scores);
402 }
403 if let Some(errors) = update.error_history {
404 self.error_history.extend(errors);
405 }
406 if let Some(signals) = update.signals {
407 self.signals.extend(signals);
408 }
409 }
410}
411
412#[cfg(test)]
413mod tests {
414 use super::*;
415 use crate::cognitive_memory::NoteCategory;
416 use crate::self_model::{FailureRecord, NegativeKnowledge};
417 use crate::state::State;
418
419 fn base_state() -> CognitiveState {
420 CognitiveState {
421 input: "analyze this".to_string(),
422 stream_outputs: HashMap::from([(
423 "logical".to_string(),
424 LobeOutput::new("logic output", 0.9).with_lobe_name("logical"),
425 )]),
426 synthesis_result: None,
427 budget_tokens: Some(1000),
428 loaded_memories: vec!["mem-1".to_string()],
429 working_notes: Vec::new(),
430 current_plan: None,
431 confidence: 0.5,
432 quality_trend: Vec::new(),
433 error_history: Vec::new(),
434 signals: Vec::new(),
435 negative_knowledge: Vec::new(),
436 failure_records: Vec::new(),
437 }
438 }
439
440 #[test]
441 fn test_apply_input_replaces() {
442 let mut state = base_state();
443 state.apply(CognitiveStateUpdate {
444 input: Some("new input".to_string()),
445 ..Default::default()
446 });
447 assert_eq!(state.input, "new input");
448 assert_eq!(state.stream_outputs.len(), 1);
450 assert_eq!(state.budget_tokens, Some(1000));
451 }
452
453 #[test]
454 fn test_apply_stream_outputs_merges() {
455 let mut state = base_state();
456 state.apply(CognitiveStateUpdate {
457 stream_outputs: Some(HashMap::from([
458 (
459 "emotional".to_string(),
460 LobeOutput::new("emotion output", 0.7).with_lobe_name("emotional"),
461 ),
462 (
463 "logical".to_string(),
464 LobeOutput::new("updated logic", 0.95).with_lobe_name("logical"),
465 ),
466 ])),
467 ..Default::default()
468 });
469 assert_eq!(
471 state.stream_outputs.get("emotional").unwrap().content,
472 "emotion output"
473 );
474 assert_eq!(
476 state.stream_outputs.get("logical").unwrap().content,
477 "updated logic"
478 );
479 assert!(
481 (state.stream_outputs.get("logical").unwrap().confidence - 0.95).abs() < f64::EPSILON
482 );
483 assert_eq!(state.stream_outputs.len(), 2);
484 }
485
486 #[test]
487 fn test_apply_synthesis_result_replaces() {
488 let mut state = base_state();
489 assert!(state.synthesis_result.is_none());
490
491 state.apply(CognitiveStateUpdate {
492 synthesis_result: Some(Some("synthesized answer".to_string())),
493 ..Default::default()
494 });
495 assert_eq!(
496 state.synthesis_result.as_deref(),
497 Some("synthesized answer")
498 );
499
500 state.apply(CognitiveStateUpdate {
502 synthesis_result: Some(None),
503 ..Default::default()
504 });
505 assert!(state.synthesis_result.is_none());
506 }
507
508 #[test]
509 fn test_apply_loaded_memories_appends() {
510 let mut state = base_state();
511 assert_eq!(state.loaded_memories, vec!["mem-1"]);
512
513 state.apply(CognitiveStateUpdate {
514 loaded_memories: Some(vec!["mem-2".to_string(), "mem-3".to_string()]),
515 ..Default::default()
516 });
517 assert_eq!(state.loaded_memories, vec!["mem-1", "mem-2", "mem-3"]);
518 }
519
520 #[test]
521 fn test_apply_none_fields_no_change() {
522 let mut state = base_state();
523 let original = state.clone();
524 state.apply(CognitiveStateUpdate::default());
525 assert_eq!(state.input, original.input);
526 assert_eq!(state.stream_outputs, original.stream_outputs);
527 assert_eq!(state.synthesis_result, original.synthesis_result);
528 assert_eq!(state.budget_tokens, original.budget_tokens);
529 assert_eq!(state.loaded_memories, original.loaded_memories);
530 }
531
532 #[test]
533 fn test_apply_multiple_fields_at_once() {
534 let mut state = base_state();
535 state.apply(CognitiveStateUpdate {
536 input: Some("new prompt".to_string()),
537 stream_outputs: Some(HashMap::from([(
538 "creative".to_string(),
539 LobeOutput::new("creative out", 0.8).with_lobe_name("creative"),
540 )])),
541 synthesis_result: Some(Some("final".to_string())),
542 budget_tokens: Some(Some(500)),
543 loaded_memories: Some(vec!["mem-4".to_string()]),
544 ..Default::default()
545 });
546 assert_eq!(state.input, "new prompt");
547 assert_eq!(state.stream_outputs.len(), 2); assert_eq!(state.synthesis_result.as_deref(), Some("final"));
549 assert_eq!(state.budget_tokens, Some(500));
550 assert_eq!(state.loaded_memories, vec!["mem-1", "mem-4"]);
551 }
552
553 #[test]
554 fn test_apply_working_notes_appends() {
555 let mut state = base_state();
556 let note = WorkingNote::new("important finding", NoteCategory::Discovery);
557 state.apply(CognitiveStateUpdate {
558 working_notes: Some(vec![note]),
559 ..Default::default()
560 });
561 assert_eq!(state.working_notes.len(), 1);
562 assert_eq!(state.working_notes[0].content, "important finding");
563
564 state.apply(CognitiveStateUpdate {
566 working_notes: Some(vec![WorkingNote::new("concern", NoteCategory::Concern)]),
567 ..Default::default()
568 });
569 assert_eq!(state.working_notes.len(), 2);
570 }
571
572 #[test]
573 fn test_apply_confidence_replaces() {
574 let mut state = base_state();
575 assert!((state.confidence - 0.5).abs() < f64::EPSILON);
576 state.apply(CognitiveStateUpdate {
577 confidence: Some(0.9),
578 ..Default::default()
579 });
580 assert!((state.confidence - 0.9).abs() < f64::EPSILON);
581 }
582
583 #[test]
584 fn test_apply_signals_appends() {
585 let mut state = base_state();
586 state.apply(CognitiveStateUpdate {
587 signals: Some(vec![CognitiveSignal::Proceed]),
588 ..Default::default()
589 });
590 state.apply(CognitiveStateUpdate {
591 signals: Some(vec![CognitiveSignal::SimplifyMode]),
592 ..Default::default()
593 });
594 assert_eq!(state.signals.len(), 2);
595 }
596
597 #[test]
598 fn test_apply_negative_knowledge_appends() {
599 use crate::self_model::Severity;
600 let mut state = base_state();
601 let nk = NegativeKnowledge::new("api", "max 100 items", Severity::High);
602 state.apply(CognitiveStateUpdate {
603 negative_knowledge: Some(vec![nk]),
604 ..Default::default()
605 });
606 assert_eq!(state.negative_knowledge.len(), 1);
607 assert_eq!(state.negative_knowledge[0].category, "api");
608 }
609
610 #[test]
611 fn test_apply_failure_records_appends() {
612 let mut state = base_state();
613 let record = FailureRecord::new("db_migration", "ALTER TABLE");
614 state.apply(CognitiveStateUpdate {
615 failure_records: Some(vec![record]),
616 ..Default::default()
617 });
618 assert_eq!(state.failure_records.len(), 1);
619 }
620
621 #[test]
622 fn test_cognitive_state_update_serialization() {
623 let update = CognitiveStateUpdate {
624 input: Some("test".to_string()),
625 ..Default::default()
626 };
627 let json = serde_json::to_string(&update).unwrap();
628 let deserialized: CognitiveStateUpdate = serde_json::from_str(&json).unwrap();
629 assert_eq!(deserialized.input.as_deref(), Some("test"));
630 assert!(deserialized.stream_outputs.is_none());
631 }
632
633 #[test]
634 fn test_replace_working_notes_overrides_existing() {
635 let mut state = base_state();
636 state.apply(CognitiveStateUpdate {
638 working_notes: Some(vec![
639 WorkingNote::new("old note 1", NoteCategory::Observation),
640 WorkingNote::new("old note 2", NoteCategory::Concern),
641 WorkingNote::new("old note 3", NoteCategory::Discovery),
642 ]),
643 ..Default::default()
644 });
645 assert_eq!(state.working_notes.len(), 3);
646
647 state.apply(CognitiveStateUpdate {
649 replace_working_notes: Some(vec![WorkingNote::new(
650 "consolidated",
651 NoteCategory::Reflection,
652 )]),
653 ..Default::default()
654 });
655 assert_eq!(state.working_notes.len(), 1);
656 assert_eq!(state.working_notes[0].content, "consolidated");
657 }
658
659 #[test]
660 fn test_replace_takes_precedence_over_append() {
661 let mut state = base_state();
662 state.apply(CognitiveStateUpdate {
663 working_notes: Some(vec![WorkingNote::new("old", NoteCategory::Observation)]),
664 ..Default::default()
665 });
666 assert_eq!(state.working_notes.len(), 1);
667
668 state.apply(CognitiveStateUpdate {
670 working_notes: Some(vec![WorkingNote::new("appended", NoteCategory::Concern)]),
671 replace_working_notes: Some(vec![WorkingNote::new("replaced", NoteCategory::Plan)]),
672 ..Default::default()
673 });
674 assert_eq!(state.working_notes.len(), 1);
675 assert_eq!(state.working_notes[0].content, "replaced");
676 }
677}