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 serialized YamlPersona object defined in src/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.jsonl or talk.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, and post_process_script.js for 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() reads manage.yaml to retrieve the current persona identifier.
  • FileManager.SetActivePersonaById(id) updates the ActivePersonaId field 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 – Invokes Persona.GetPersonas() (lines 315-332) to return a list of all personas with their IDs, names, and last-talk timestamps.
  • POST /newPersona – Calls Persona.NewPersona(name) (lines 33-46) to create a new directory with a default YamlPersona configuration.
  • POST /duplicatePersona – Executes Persona.DuplicatePersona(id, newName?) to clone an existing persona's folder structure and settings.
  • POST /removePersona – Runs Persona.RemovePersona(id) to delete the persona's directory and associated data.
  • GET /activePersona and POST /setActivePersona – Query or modify the active persona ID through Persona.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:

  1. Persona.UpsertActivePersonaMessage() stores incoming messages in the active persona's talk database.
  2. LLM.GenerateResponseAsync() consults personaSettings.Model and other LLM-specific options from the active YamlPersona.
  3. For timer-based generation, Persona.PerformPeriodicTasks() checks EnableTimerGenerate and inserts system messages using the persona's TimerGenerateMessage field.

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>/) containing persona.yaml, talk history, and attachments, defined in src/FileManager.cs.
  • Active state tracking: The current persona ID is persisted in data/manage.yaml and managed through FileManager.SetActivePersonaById().
  • RESTful lifecycle management: src/Persona.cs exposes CRUD operations via HTTP endpoints, including creation, duplication, deletion, and switching.
  • Scoped LLM configuration: Each YamlPersona instance specifies model selection, timer behavior, and generation parameters that LLM.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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →