oxios_kernel/
persona_manager.rs1use anyhow::Result;
7use parking_lot::RwLock;
8
9use super::persona::{default_personas, Persona};
10use super::persona_store::PersonaStore;
11
12#[derive(Debug)]
14pub struct PersonaManager {
15 store: PersonaStore,
16 active_persona_id: RwLock<Option<String>>,
17}
18
19impl PersonaManager {
20 pub fn new() -> Self {
22 let store = PersonaStore::new();
23 let manager = Self {
24 store,
25 active_persona_id: RwLock::new(None),
26 };
27 manager.create_default_personas();
28 manager
29 }
30
31 pub fn with_defaults(personas: Vec<Persona>) -> Self {
33 let store = PersonaStore::new();
34 store.load_from_slice(&personas);
35 let this = Self {
36 store,
37 active_persona_id: RwLock::new(None),
38 };
39 if let Some(first) = this.store.list_enabled().into_iter().next() {
41 *this.active_persona_id.write() = Some(first.id);
42 }
43 this
44 }
45
46 pub fn get_active_persona(&self) -> Option<Persona> {
48 let active_id = self.active_persona_id.read().clone();
49 active_id.and_then(|id| self.store.get(&id))
50 }
51
52 pub fn set_active_persona(&self, id: &str) -> Result<()> {
54 let persona = self
56 .store
57 .get(id)
58 .ok_or_else(|| anyhow::anyhow!("Persona '{}' not found", id))?;
59 if !persona.enabled {
60 anyhow::bail!("Persona '{}' is disabled", id);
61 }
62 *self.active_persona_id.write() = Some(id.to_string());
63 tracing::info!(persona_id = %id, name = %persona.name, "Active persona set");
64 Ok(())
65 }
66
67 pub fn active_system_prompt(&self) -> String {
70 self.get_active_persona()
71 .map(|p| p.system_prompt.clone())
72 .unwrap_or_else(|| {
73 "You are a helpful AI assistant that follows the Ouroboros methodology: \
74 specify before you build, evaluate before you ship."
75 .to_string()
76 })
77 }
78
79 pub fn create_default_personas(&self) {
81 let defaults = default_personas();
82 for persona in defaults {
83 if self.store.get(&persona.id).is_none() {
85 self.store.register(persona);
86 }
87 }
88 {
90 let mut active = self.active_persona_id.write();
91 if active.is_none() {
92 *active = Some("dev".to_string());
93 }
94 }
95 tracing::info!("Default personas initialized");
96 }
97
98 pub fn first_enabled(&self) -> Option<Persona> {
100 self.store.list_enabled().into_iter().next()
101 }
102
103 pub fn store(&self) -> &PersonaStore {
105 &self.store
106 }
107
108 pub fn active_persona_id(&self) -> Option<String> {
110 self.active_persona_id.read().clone()
111 }
112}
113
114impl Default for PersonaManager {
115 fn default() -> Self {
116 Self::new()
117 }
118}
119
120impl Clone for PersonaManager {
121 fn clone(&self) -> Self {
122 let personas: Vec<Persona> = self.store.list_all();
123 let store = PersonaStore::new();
124 store.load_from_slice(&personas);
125 Self {
126 store,
127 active_persona_id: RwLock::new(self.active_persona_id.read().clone()),
128 }
129 }
130}