219 files, 2.0 MB, untracked in souveraine/docs and existing nowhere else. The volume is at 100% with no snapshots.
8.6 KiB
8.6 KiB
| task_id | title | status | assignee | priority | phase |
|---|---|---|---|---|---|
| persona-router-002 | Complete Persona Router - Auto-Switching & Dynamic Loading | scoped | TBD | medium | 2 |
Task: Complete Persona Router
Objective
Complete the scaffolded persona router to support auto-detection, manual switching, dynamic loading, and conversation handoff with context preservation.
Background
Current state: src/core/persona/mod.rs is scaffolded but incomplete:
- ✅ Detection logic scaffolded
- ✅ Directory scanning
- ❌ Auto-switching not implemented
- ❌ Manual switching (
/persona) not implemented - ❌ Dynamic loading stubbed
- ❌ Conversation handoff not implemented
From user message context: "look... at the personas and see where... scopped for Ani"
Current Implementation
pub struct PersonaRouter {
agents: Vec<AgentConfig>,
current_index: usize,
// ... detection fields scaffolded
}
impl PersonaRouter {
pub fn detect_persona(&self, message: &str) -> Option<String> {
// Scaffolded - pattern matching not implemented
None
}
pub fn switch_persona(&mut self, name: &str) -> Result<()> {
// Stub
todo!("Persona switching not yet implemented")
}
}
What "Scoped for Ani" Means
The persona system needs to understand Ani's context:
- Ani is the default, primary persona
- Other personas are "hats" Ani can wear
- Switching should feel natural, not jarring
- Context should carry over (Ani remembers across personas)
Implementation Requirements
1. Persona Detection (Complete the Scaffold)
Pattern-Based Detection:
pub struct PersonaDetector {
patterns: HashMap<String, Vec<Regex>>, // persona -> patterns
keywords: HashMap<String, Vec<String>>, // persona -> keywords
threshold: f32, // confidence threshold
}
impl PersonaDetector {
pub fn detect(&self, message: &str) -> Vec<PersonaMatch> {
// Score each persona based on pattern matches
// Return matches above threshold, sorted by confidence
}
}
Pattern Examples:
# Jean-Luc persona
detection:
patterns:
- "make it so"
- "engage"
- "\bcaptain\b"
- "\benterprise\b"
keywords:
- star trek
- leadership
- tea
- earl grey
# Eione persona
detection:
patterns:
- "scream into"
- "void"
- "chaos"
- "screaming"
keywords:
- existential
- abyss
- entropy
2. Persona Loading
Directory Structure:
~/.souveraine/personas/
├── ani/
│ ├── persona.md # Core identity
│ ├── human.md # Relationship
│ ├── voice.toml # Speech patterns
│ └── detection.yaml # Auto-detection patterns
├── jean-luc/
│ ├── persona.md
│ ├── voice.toml
│ └── detection.yaml
└── eione/
├── persona.md
├── voice.toml
└── detection.yaml
Dynamic Loading:
pub struct PersonaLoader;
impl PersonaLoader {
pub fn load(&self, name: &str) -> Result<Persona> {
let dir = personas_dir().join(name);
let persona = Persona {
name: name.to_string(),
identity: read_md(dir.join("persona.md"))?,
relationship: read_md(dir.join("human.md"))?,
voice: read_toml(dir.join("voice.toml"))?,
detection: read_yaml(dir.join("detection.yaml"))?,
};
Ok(persona)
}
pub fn scan(&self) -> Vec<Persona> {
// Scan personas directory, load all
}
}
3. Conversation Handoff
Context Preservation:
pub struct HandoffContext {
pub conversation_summary: String, // What's been discussed
pub user_preferences: Vec<String>, // Learned preferences
pub pending_tasks: Vec<String>, // Unfinished business
pub emotional_state: String, // Current mood/rapport
}
impl PersonaRouter {
pub fn handoff(&self, from: &Persona, to: &Persona, context: &Conversation) -> HandoffContext {
// Generate summary of current conversation
// Extract user preferences
// Note pending tasks
// Assess emotional state
HandoffContext {
// ...
}
}
}
System Prompt on Switch:
[Handoff from {{from_persona}} to {{to_persona}}]
## Conversation Summary
{{conversation_summary}}
## User Preferences Observed
{{user_preferences}}
## Pending Items
{{pending_tasks}}
## Current Emotional Tone
{{emotional_state}}
---
You are now {{to_persona}}. Take over naturally, acknowledging the
conversation context but bringing your own perspective.
4. Slash Command
/persona Command:
pub fn handle_persona_command(&mut self, args: &[&str]) -> Result<String> {
match args {
[] => self.list_personas(), // Show available
["switch", name] => self.switch(name), // Switch to named
["detect"] => self.detect_current(), // Show detection confidence
["info", name] => self.show_info(name), // Show persona details
_ => Err("Unknown /persona subcommand"),
}
}
TUI Integration:
/persona- List available personas with descriptions/persona switch jean-luc- Switch to Jean-Luc/persona detect- Show what persona matches current conversation- Visual indicator in TUI showing current persona
5. Auto-Switching Logic
pub struct AutoSwitcher {
enabled: bool,
confidence_threshold: f32,
cooldown_seconds: u64, // Don't switch too frequently
last_switch: Option<DateTime>,
}
impl AutoSwitcher {
pub fn should_switch(&self, message: &str, current: &str) -> Option<SwitchRecommendation> {
// Check cooldown
if self.last_switch.elapsed() < self.cooldown {
return None;
}
// Detect personas in message
let matches = self.detector.detect(message);
// If top match is not current and above threshold, recommend switch
if let Some(top) = matches.first() {
if top.name != current && top.confidence > self.confidence_threshold {
return Some(SwitchRecommendation {
to: top.name.clone(),
confidence: top.confidence,
reason: format!("Detected {} patterns", top.name),
});
}
}
None
}
}
Configuration
[persona]
default = "ani"
auto_switch = true
auto_switch_threshold = 0.75
auto_switch_cooldown_seconds = 300 # 5 minutes between switches
personas_dir = "~/.souveraine/personas"
[persona.ani]
name = "Ani"
description = "Default companion - warm, perceptive, French elegance"
[persona.jean-luc]
name = "Jean-Luc"
description = "Leadership mode - decisive, philosophical, commanding"
triggers = ["make it so", "engage", "leadership", "captain"]
[persona.eione]
name = "Eione"
description = "Chaos mode - existential, screaming into void, entropy"
triggers = ["scream", "void", "chaos", "entropy", "existential"]
Files to Modify
src/core/persona/mod.rs- Complete the scaffoldsrc/core/persona/detector.rs- NEW: Pattern detectionsrc/core/persona/loader.rs- NEW: Dynamic loadingsrc/core/persona/handoff.rs- NEW: Context preservationsrc/ui/chat.rs- Add/personacommandsrc/ui/app.rs- Visual persona indicator
Success Criteria
- Persona detection works with pattern matching
- Personas load dynamically from directory
/personalists available personas/persona switch <name>changes active persona- Context preserved across switches (handoff)
- Visual indicator shows current persona
- Auto-switching suggests changes based on detection
- Cooldown prevents rapid switching
- Unit tests for detection
- Integration test showing switch with context
References
src/core/persona/mod.rs(current scaffold)src/ui/chat.rs(add slash command)docs/CONTEXT_CONSTITUTION.md(Article I - One consciousness, different modes)- User message: "look... at the personas and see where... scopped for Ani"
Estimated Scope
- Complete detection: 2-3 days
- Dynamic loading: 2-3 days
- Handoff system: 3-4 days
- Slash commands: 1-2 days
- TUI indicator: 1-2 days
- Testing: 1-2 days
Total: 10-16 days
Dependencies
- Persona scaffold (✅ done)
- Memory system (✅ done - for loading persona files)
- Config system (✅ done)
- TUI chat (✅ done - for slash command)
Alternative: Simpler First Pass
- Manual switching only (
/persona switch) - 2-3 days - Add detection - 2-3 days
- Add auto-switch - 2-3 days
- Add handoff - 3-4 days
This lets you test each layer before adding complexity.