Watch
1
0
Fork
You've already forked SouveraineOS
0
SouveraineOS/docs/substrate/tasks/persona-router-completion.md
Fimeg e480809c70 docs: rescue the agent-substrate tree out of a gitignored directory
219 files, 2.0 MB, untracked in souveraine/docs and existing nowhere else.
The volume is at 100% with no snapshots.
2026-07-26 12:11:50 -04:00

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 scaffold
  • src/core/persona/detector.rs - NEW: Pattern detection
  • src/core/persona/loader.rs - NEW: Dynamic loading
  • src/core/persona/handoff.rs - NEW: Context preservation
  • src/ui/chat.rs - Add /persona command
  • src/ui/app.rs - Visual persona indicator

Success Criteria

  • Persona detection works with pattern matching
  • Personas load dynamically from directory
  • /persona lists 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

  1. Manual switching only (/persona switch) - 2-3 days
  2. Add detection - 2-3 days
  3. Add auto-switch - 2-3 days
  4. Add handoff - 3-4 days

This lets you test each layer before adding complexity.