How ChocolateLMLite Manages Conversation Personas: File-Based Isolation and Runtime Switching
ChocolateLMLite manages conversation personas as self-contained state bundles stored in dedicated folders under data/persona_<id>/, allowing instant switching via the ActivePersonaId tracker in manage.yaml while maintaining isolated talk histories, system prompts, and model configurations for each identity.
ChocolateLMLite, an open-source LLM client framework developed by gpsnmeajp, implements conversation personas as first-class architectural entities rather than simple prompt prefixes. The system manages conversation personas through a file-based isolation model that persists state, history, and behavior configuration into discrete, swappable units on disk. This design enables users to maintain multiple distinct AI identities—each with unique memories, attachments, and generation parameters—while switching between them at runtime without data loss.
Persona Storage Architecture
Each conversation persona resides in a dedicated directory structure under the data folder, specifically at data/persona_<id>/. This directory acts as a complete container for that identity's state and configuration.
The folder contains several critical files:
persona.yaml– A serializedYamlPersonaobject defined insrc/FileManager.cs(lines 17-47) that stores the persona's name, selected LLM model, timer cycle settings, webhook configuration, dynamic-context options, and VoiceVox speech parameters.talk.jsonlortalk.sqlite3– The persistent talk history database for that specific persona, ensuring conversation continuity across sessions.attachments/– A subdirectory for files the persona can reference during conversations.- Optional configuration files – Including
memory.yaml,summary.yaml,system_prompt.txt, andpost_process_script.jsfor advanced customization.
This file-based approach treats each persona as a portable, self-contained bundle that can be duplicated, backed up, or versioned independently.
Active Persona Tracking and Switching
The system maintains a single source of truth for the currently active persona in data/manage.yaml. This file stores the ActivePersonaId field, which determines which persona's directory is currently loaded into memory.
The FileManager class handles persistence operations:
FileManager.GetActivePersonaId()readsmanage.yamlto retrieve the current persona identifier.FileManager.SetActivePersonaById(id)updates theActivePersonaIdfield and clears the LLM cache to prevent context leakage between identities.
When a switch occurs, the Persona class (defined in src/Persona.cs) notifies the ConsoleMonitor UI via NotifyPersonaInfo(), ensuring the interface reflects the new active state immediately.
Persona Lifecycle and Web API Operations
The WebServer class exposes a JSON-RPC-style HTTP interface that routes client requests to the Persona class facade. This architecture separates transport concerns from business logic while providing full CRUD capabilities.
Key endpoints implemented in src/Persona.cs include:
GET /personas– InvokesPersona.GetPersonas()(lines 315-332) to return a list of all personas with their IDs, names, and last-talk timestamps.POST /newPersona– CallsPersona.NewPersona(name)(lines 33-46) to create a new directory with a defaultYamlPersonaconfiguration.POST /duplicatePersona– ExecutesPersona.DuplicatePersona(id, newName?)to clone an existing persona's folder structure and settings.POST /removePersona– RunsPersona.RemovePersona(id)to delete the persona's directory and associated data.GET /activePersonaandPOST /setActivePersona– Query or modify the active persona ID throughPersona.SetActivePersona()(lines 88-100).
Per-Persona Configuration and LLM Integration
Each persona's behavior is governed by its YamlPersona instance, accessible via Persona.GetActivePersonaSettings() and mutable through Persona.SetActivePersonaSettings(). The settings include the target LLM model, timer generation intervals, webhook endpoints, and VoiceVox voice parameters.
When the system generates responses, the active persona's configuration directly influences the inference pipeline:
Persona.UpsertActivePersonaMessage()stores incoming messages in the active persona's talk database.LLM.GenerateResponseAsync()consultspersonaSettings.Modeland other LLM-specific options from the activeYamlPersona.- For timer-based generation,
Persona.PerformPeriodicTasks()checksEnableTimerGenerateand inserts system messages using the persona'sTimerGenerateMessagefield.
This tight integration ensures that model selection, context window management, and generation parameters remain scoped to the active identity.
Practical API Examples
Listing All Personas
GET http://localhost:8010/personas
Response:
{
"personas": [
{ "id": 1, "name": "Default", "timestamp": 1719951234 },
{ "id": 2, "name": "Researcher", "timestamp": 1720015678 }
],
"count": 2,
"active": 1
}
This endpoint invokes FileManager.GetPersonaListWithNamesAndLastTimestamp() to aggregate metadata from each persona_<id>/ directory.
Creating a New Persona
POST http://localhost:8010/newPersona
Content-Type: application/json
{
"name": "Storyteller"
}
Response:
{ "id": 3 }
The Persona.NewPersona() method calls FileManager.CreateNewPersona() to initialize data/persona_3/ with a default persona.yaml configuration.
Switching the Active Persona
POST http://localhost:8010/setActivePersona
Content-Type: application/json
{
"id": 2
}
Response:
{ "id": 2 }
This triggers FileManager.SetActivePersonaById(), which atomically updates data/manage.yaml and clears the inference cache to prevent cross-contamination between personas.
Updating Timer Settings
POST http://localhost:8010/setActivePersonaSettings
Content-Type: application/json
{
"timer_cycle_minutes": 30,
"timer_generate_message": "It's time for a quick check‑in."
}
Response:
{ "success": "done" }
The implementation in Persona.SetActivePersonaSettings() (lines 662-704) parses the JSON payload, updates the YamlPersona fields, and persists changes via FileManager.SaveActivePersonaSettings().
Sending a Message to the Active Persona
POST http://localhost:8010/upsertActivePersonaMessage
Content-Type: application/json
{
"Role": "User",
"Text": "How does the weather look today?"
}
Response:
{ "success": "done", "uuid": "c3f5b2e0‑9d8a‑4d6f‑a2f6‑a0d5e8b3c7a1" }
Persona.UpsertActivePersonaMessage() appends the entry to the active persona's talk history and initiates LLM.GenerateResponseAsync() using that persona's specific model configuration.
Summary
- File-based isolation: Each persona exists as a distinct directory (
data/persona_<id>/) containingpersona.yaml, talk history, and attachments, defined insrc/FileManager.cs. - Active state tracking: The current persona ID is persisted in
data/manage.yamland managed throughFileManager.SetActivePersonaById(). - RESTful lifecycle management:
src/Persona.csexposes CRUD operations via HTTP endpoints, including creation, duplication, deletion, and switching. - Scoped LLM configuration: Each
YamlPersonainstance specifies model selection, timer behavior, and generation parameters thatLLM.GenerateResponseAsync()uses exclusively for that identity. - Runtime switching: Changing personas updates the active ID and clears caches without losing historical conversation data.
Frequently Asked Questions
Where does ChocolateLMLite store individual persona configurations?
Each persona's configuration is stored in data/persona_<id>/persona.yaml as a serialized YamlPersona object. This file, defined in src/FileManager.cs (lines 17-47), contains the persona's name, model selection, timer settings, webhook URLs, and VoiceVox parameters. Additional per-persona data like talk history resides in talk.jsonl or talk.sqlite3 within the same directory.
How do I programmatically switch between conversation personas?
Send a POST request to the /setActivePersona endpoint with the target ID in the request body. This invokes Persona.SetActivePersona() in src/Persona.cs (lines 88-100), which calls FileManager.SetActivePersonaById() to update data/manage.yaml and clears the LLM cache to ensure clean context separation between identities.
Can different personas use different LLM models simultaneously?
Yes. Each YamlPersona instance includes a Model field that specifies which model to use for that identity. When Persona.UpsertActivePersonaMessage() triggers generation, LLM.GenerateResponseAsync() consults the active persona's settings to determine the endpoint and model parameters, allowing one persona to use GPT-4 while another uses a local Llama instance.
What happens to conversation history when switching personas?
Conversation history remains permanently isolated within each persona's directory in talk.jsonl or talk.sqlite3 files. Switching personas via SetActivePersonaById() only updates the ActivePersonaId reference in manage.yaml and clears the transient LLM cache; it does not modify or delete historical data. Users can switch back to previous personas and resume conversations exactly where they left off.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →