Skip to main content

oxios_kernel/kernel_handle/
persona_api.rs

1//! Persona API — multi-persona management.
2//!
3//! `PersonaApi` is the public surface over `PersonaManager`. RFC-039 adds
4//! `set_active_with_persist` + `persist`, which also flushes the full
5//! registry via the shared `StateApi`, and `manager()` for callers that
6//! need the underlying `Arc<PersonaManager>` (e.g. boot-time
7//! `load_from_state_store` / `apply_config`).
8
9use crate::persona::Persona;
10use crate::persona::PersonaManager;
11use std::sync::Arc;
12
13/// Persona management system calls.
14pub struct PersonaApi {
15    pub(crate) persona_manager: Arc<PersonaManager>,
16    /// RFC-039: optional callback invoked after a successful activate/persist
17    /// to re-seed the intent engine's system_prompt.
18    reseed_callback: Option<Arc<dyn Fn(Option<String>) + Send + Sync>>,
19}
20
21impl PersonaApi {
22    /// Create a new PersonaApi.
23    pub fn new(persona_manager: Arc<PersonaManager>) -> Self {
24        Self {
25            persona_manager,
26            reseed_callback: None,
27        }
28    }
29
30    /// Set the callback that re-seeds the intent engine's system_prompt.
31    /// Called automatically by `set_active_with_persist`.
32    pub fn set_reseed_callback(&mut self, cb: Option<Arc<dyn Fn(Option<String>) + Send + Sync>>) {
33        self.reseed_callback = cb;
34    }
35
36    /// Underlying `Arc<PersonaManager>` for boot-time wiring.
37    pub fn manager(&self) -> Arc<PersonaManager> {
38        Arc::clone(&self.persona_manager)
39    }
40
41    /// List all personas.
42    pub fn list(&self) -> Vec<Persona> {
43        self.persona_manager.store().list_all()
44    }
45
46    /// Get persona by ID.
47    pub fn get(&self, id: &str) -> Option<Persona> {
48        self.persona_manager.store().get(id)
49    }
50
51    /// Create a new persona (in-memory only; persistence requires an
52    /// explicit `persist` call after this — see `handle_persona_create`).
53    pub fn create(&self, persona: Persona) {
54        self.persona_manager.store().register(persona);
55    }
56
57    /// Update a persona (in-memory only; call `persist` after).
58    pub fn update(&self, id: &str, persona: Persona) -> anyhow::Result<()> {
59        self.persona_manager.store().update(id, persona)
60    }
61
62    /// Delete a persona (in-memory only; call `persist` after).
63    pub fn delete(&self, id: &str) -> anyhow::Result<()> {
64        self.persona_manager.store().delete(id)
65    }
66
67    /// Get active persona.
68    pub fn active(&self) -> Option<Persona> {
69        self.persona_manager.get_active_persona()
70    }
71
72    /// Get active persona ID.
73    pub fn active_id(&self) -> Option<String> {
74        self.persona_manager.active_persona_id()
75    }
76
77    /// Legacy in-memory `set_active` (no persistence).
78    /// Prefer `set_active_with_persist` for new callers.
79    pub fn set_active(&self, id: &str) -> anyhow::Result<()> {
80        self.persona_manager.set_active_persona(id)
81    }
82
83    /// RFC-039: set active persona + persist the full registry via `StateApi`.
84    /// Returns the new system_prompt so the caller can re-seed the intent
85    /// engine (kernel <-> ouroboros dependency direction avoidance).
86    pub async fn set_active_with_persist(
87        &self,
88        id: &str,
89        state_api: &crate::kernel_handle::StateApi,
90    ) -> anyhow::Result<Option<String>> {
91        self.persona_manager.set_active(id, None).await?;
92        self.persist(state_api).await?;
93        let prompt = self
94            .persona_manager
95            .get_active_persona()
96            .map(|p| p.system_prompt);
97        // RFC-039: auto re-seed intent engine if callback is set.
98        if let Some(ref cb) = self.reseed_callback {
99            cb(prompt.clone());
100        }
101        Ok(prompt)
102    }
103
104    /// RFC-039: persist the full persona registry to `StateStore`.
105    /// Call this after every mutation (`create`/`update`/`delete`) so the
106    /// in-memory state matches the on-disk state. Otherwise the next
107    /// restart will lose the change.
108    pub async fn persist(&self, state_api: &crate::kernel_handle::StateApi) -> anyhow::Result<()> {
109        let snapshot = crate::persona::persistence::PersonaSnapshot {
110            schema_version: 1,
111            active_persona_id: self.persona_manager.active_persona_id(),
112            personas: self.persona_manager.store().list_all(),
113        };
114        state_api.save("personas", "index", &snapshot).await
115    }
116
117    /// Get persona count.
118    pub fn count(&self) -> usize {
119        self.persona_manager.store().len()
120    }
121
122    /// List enabled personas.
123    pub fn list_enabled(&self) -> Vec<Persona> {
124        self.persona_manager.store().list_enabled()
125    }
126}