# How ChocolateLMLite Manages Conversation Personas: File-Based Isolation and Runtime Switching

> Discover how ChocolateLMLite manages conversation personas with file-based isolation and runtime switching. Explore self-contained state bundles and instant identity changes for distinct talk histories and model configurations.

- Repository: [Segment (gpsnmeajp)/chocolatelmlite](https://github.com/gpsnmeajp/chocolatelmlite)
- Tags: internals
- Published: 2026-03-02

---

**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`](https://github.com/gpsnmeajp/chocolatelmlite/blob/main/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`](https://github.com/gpsnmeajp/chocolatelmlite/blob/main/persona.yaml)** – A serialized `YamlPersona` object defined in [`src/FileManager.cs`](https://github.com/gpsnmeajp/chocolatelmlite/blob/main/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`](https://github.com/gpsnmeajp/chocolatelmlite/blob/main/memory.yaml), [`summary.yaml`](https://github.com/gpsnmeajp/chocolatelmlite/blob/main/summary.yaml), [`system_prompt.txt`](https://github.com/gpsnmeajp/chocolatelmlite/blob/main/system_prompt.txt), and [`post_process_script.js`](https://github.com/gpsnmeajp/chocolatelmlite/blob/main/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`](https://github.com/gpsnmeajp/chocolatelmlite/blob/main/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`](https://github.com/gpsnmeajp/chocolatelmlite/blob/main/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`](https://github.com/gpsnmeajp/chocolatelmlite/blob/main/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`](https://github.com/gpsnmeajp/chocolatelmlite/blob/main/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

```http
GET http://localhost:8010/personas

```

**Response:**

```json
{
  "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

```http
POST http://localhost:8010/newPersona
Content-Type: application/json

{
  "name": "Storyteller"
}

```

**Response:**

```json
{ "id": 3 }

```

The `Persona.NewPersona()` method calls `FileManager.CreateNewPersona()` to initialize `data/persona_3/` with a default [`persona.yaml`](https://github.com/gpsnmeajp/chocolatelmlite/blob/main/persona.yaml) configuration.

### Switching the Active Persona

```http
POST http://localhost:8010/setActivePersona
Content-Type: application/json

{
  "id": 2
}

```

**Response:**

```json
{ "id": 2 }

```

This triggers `FileManager.SetActivePersonaById()`, which atomically updates [`data/manage.yaml`](https://github.com/gpsnmeajp/chocolatelmlite/blob/main/data/manage.yaml) and clears the inference cache to prevent cross-contamination between personas.

### Updating Timer Settings

```http
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:**

```json
{ "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

```http
POST http://localhost:8010/upsertActivePersonaMessage
Content-Type: application/json

{
  "Role": "User",
  "Text": "How does the weather look today?"
}

```

**Response:**

```json
{ "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`](https://github.com/gpsnmeajp/chocolatelmlite/blob/main/persona.yaml), talk history, and attachments, defined in [`src/FileManager.cs`](https://github.com/gpsnmeajp/chocolatelmlite/blob/main/src/FileManager.cs).
- **Active state tracking**: The current persona ID is persisted in [`data/manage.yaml`](https://github.com/gpsnmeajp/chocolatelmlite/blob/main/data/manage.yaml) and managed through `FileManager.SetActivePersonaById()`.
- **RESTful lifecycle management**: [`src/Persona.cs`](https://github.com/gpsnmeajp/chocolatelmlite/blob/main/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`](https://github.com/gpsnmeajp/chocolatelmlite/blob/main/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`](https://github.com/gpsnmeajp/chocolatelmlite/blob/main/src/Persona.cs) (lines 88-100), which calls `FileManager.SetActivePersonaById()` to update [`data/manage.yaml`](https://github.com/gpsnmeajp/chocolatelmlite/blob/main/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`](https://github.com/gpsnmeajp/chocolatelmlite/blob/main/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.