# Timer Functionality for Autonomous Speech in ChocolateLMLite: Complete Implementation Guide

> Implement timer functionality for autonomous speech in ChocolateLMLite. This guide explains how the background timer generates AI speech automatically by injecting system messages. Learn how to enable timer generation.

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

---

**ChocolateLMLite automatically generates AI speech at configurable intervals through a background timer that injects system messages into the active persona's conversation history when `EnableTimerGenerate` is enabled.**

ChocolateLMLite includes a sophisticated timer system that enables autonomous speech generation without user prompts. This feature, implemented in the `gpsnmeajp/chocolatelmlite` repository, allows the LLM to initiate conversations periodically based on settings defined in [`FileManager.cs`](https://github.com/gpsnmeajp/chocolatelmlite/blob/main/FileManager.cs) and executed through the `Persona` class background loop.

## Core Components of the Timer System

The autonomous speech feature relies on four key configuration values managed by [`FileManager.cs`](https://github.com/gpsnmeajp/chocolatelmlite/blob/main/FileManager.cs) and consumed by the periodic task runner.

**Global Toggle and Limits in [`FileManager.cs`](https://github.com/gpsnmeajp/chocolatelmlite/blob/main/FileManager.cs)**

- **`EnableTimer`** (lines 178-188): The master switch stored in [`settings.yaml`](https://github.com/gpsnmeajp/chocolatelmlite/blob/main/settings.yaml) that activates the entire timer functionality across all personas.
- **`TimerGenerateLimitMax`** (lines 101-102): A safety upper bound preventing runaway generation by limiting consecutive timer-triggered messages.
- **`TimerGenerateMessage`** (lines 101-103): The template text wrapped in `<system>` tags that precedes each autonomous generation.

**Per-Persona Configuration**

- **`TimerCycleMinutes`** (lines 21-22): Defines the interval between autonomous speeches for each individual persona, stored in `persona_<id>.yaml`.

**Execution Engine**

- **`PerformPeriodicTasks`** in [`Persona.cs`](https://github.com/gpsnmeajp/chocolatelmlite/blob/main/Persona.cs) (lines 33-45): The core background method executed every second that checks timing conditions and triggers generation.

## How the Timer Works Internally

The autonomous speech system operates through a continuous background loop that evaluates time-based conditions against the active persona's settings.

### The Background Execution Loop

Every second, the `Persona.PerformPeriodicTasks` method performs the following validation sequence:

1. Checks if `generalSettings.EnableTimerGenerate` is **true**.
2. Retrieves the active persona configuration via `fileManager.GetActivePersonaSettings()`.
3. Verifies that `TimerCycleMinutes` is greater than zero.
4. Calculates the next scheduled execution time:

```csharp
var nextGeneratedAt = lastGeneratedAt.AddMinutes(activePersonaSettings.TimerCycleMinutes);

```

This logic appears at lines 34-40 of [`Persona.cs`](https://github.com/gpsnmeajp/chocolatelmlite/blob/main/Persona.cs).

### Message Generation and Injection

When `DateTime.UtcNow` exceeds `nextGeneratedAt`, the timer fires and executes the following sequence (lines 63-70 of [`Persona.cs`](https://github.com/gpsnmeajp/chocolatelmlite/blob/main/Persona.cs)):

- Respects the `TimerGenerateLimitMax` constraint to prevent excessive consecutive generations.
- Skips execution if the LLM is already processing (`llm.IsGenerating()` returns true).
- Constructs a system entry using the configured message template:

```csharp
string tt = $"<system>{fileManager.generalSettings.TimerGenerateMessage}</system>";

```

- Inserts the entry into the active persona's talk history with `TalkRole.ChocolateLM` and the current Unix timestamp.
- Invokes `llm.GenerateResponseAsync()` to produce the autonomous speech.
- Updates counters (`lastGeneratedAt`, `consecutiveTimerGenerations`) for the next interval calculation.

## Configuring Autonomous Speech

You can enable and customize timer-generated speech through YAML configuration files or the web interface defined in [`static/js/setting.js`](https://github.com/gpsnmeajp/chocolatelmlite/blob/main/static/js/setting.js).

### YAML Configuration (Manual)

Edit [`data/settings.yaml`](https://github.com/gpsnmeajp/chocolatelmlite/blob/main/data/settings.yaml) for global settings and [`data/persona_1.yaml`](https://github.com/gpsnmeajp/chocolatelmlite/blob/main/data/persona_1.yaml) (or your specific persona file) for interval definitions:

```yaml

# data/settings.yaml

EnableTimerGenerate: true
TimerGenerateLimitMax: 30
TimerGenerateMessage: "タイマーイベント: 自由に独り言を言ったり、ツールを呼び出したりすることが出来ます。"

# data/persona_1.yaml

TimerCycleMinutes: 10

```

### Web Interface Configuration

The front-end exposes these controls through [`static/js/setting.js`](https://github.com/gpsnmeajp/chocolatelmlite/blob/main/static/js/setting.js) (lines 151-164):

1. Navigate to **Settings** → **General**.
2. Enable **Timer Generate** to set `EnableTimerGenerate` to true.
3. Enter the desired interval in the **Timer Cycle (min)** field to set `TimerCycleMinutes`.
4. Click **Save** to persist values through the `/api/general/setting` endpoint.

### REST API Updates

Update timer settings programmatically via the active persona endpoint:

```bash
curl -X POST http://localhost:8010/api/persona/active/setting \
    -H "Content-Type: application/json" \
    -d '{
          "timer_cycle_minutes": 15,
          "enable_timer_generate": true
        }'

```

This modifies the active persona's YAML file immediately; the background task will begin emitting messages every 15 minutes.

## Implementation Code Examples

### Minimal C# Timer Check Logic

The following snippet replicates the core validation logic from [`Persona.cs`](https://github.com/gpsnmeajp/chocolatelmlite/blob/main/Persona.cs):

```csharp
if (generalSettings.EnableTimerGenerate && activePersonaSettings.TimerCycleMinutes > 0)
{
    var next = lastGeneratedAt.AddMinutes(activePersonaSettings.TimerCycleMinutes);
    if (DateTime.UtcNow >= next && !llm.IsGenerating())
    {
        // Insert system entry
        var sys = $"<system>{generalSettings.TimerGenerateMessage}</system>";
        var entry = new TalkEntry { 
            Role = TalkRole.ChocolateLM, 
            Text = sys, 
            Timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds() 
        };
        await fileManager.UpsertTalkHistoryToActivePersona(entry);
        await llm.GenerateResponseAsync(); // autonomous speech generation
    }
}

```

### Configuration File Structure

The timer system relies on specific YAML structures parsed by [`FileManager.cs`](https://github.com/gpsnmeajp/chocolatelmlite/blob/main/FileManager.cs):

- **Global settings** ([`settings.yaml`](https://github.com/gpsnmeajp/chocolatelmlite/blob/main/settings.yaml)): Contains `EnableTimerGenerate`, `TimerGenerateLimitMax`, and `TimerGenerateMessage`.
- **Persona files** (`persona_*.yaml`): Contains `TimerCycleMinutes` and webhook settings.

## Summary

- **EnableTimerGenerate** in [`settings.yaml`](https://github.com/gpsnmeajp/chocolatelmlite/blob/main/settings.yaml) acts as the master switch for autonomous speech.
- **TimerCycleMinutes** controls the interval between generations for each persona.
- **PerformPeriodicTasks** in [`Persona.cs`](https://github.com/gpsnmeajp/chocolatelmlite/blob/main/Persona.cs) executes every second to evaluate timing conditions.
- **TimerGenerateLimitMax** prevents infinite generation loops by capping consecutive calls.
- The system injects **TimerGenerateMessage** wrapped in `<system>` tags to trigger LLM responses without user input.
- Configuration persists through YAML files or the web interface at [`static/js/setting.js`](https://github.com/gpsnmeajp/chocolatelmlite/blob/main/static/js/setting.js).

## Frequently Asked Questions

### How do I completely disable the autonomous speech timer?

Set `EnableTimerGenerate: false` in [`data/settings.yaml`](https://github.com/gpsnmeajp/chocolatelmlite/blob/main/data/settings.yaml) or uncheck the **Enable Timer Generate** option in the web UI. This prevents `PerformPeriodicTasks` from evaluating timer conditions entirely.

### What happens if the LLM is still generating when the timer fires?

The timer checks `llm.IsGenerating()` before creating new entries. If generation is in progress, the timer skips the current cycle and rechecks after one second, ensuring no overlapping requests occur.

### Can different personas have different autonomous speech intervals?

Yes. `TimerCycleMinutes` is stored per persona in individual `persona_<id>.yaml` files. When you switch active personas, `fileManager.GetActivePersonaSettings()` loads the specific interval for that personality.

### What is the purpose of the `<system>` tag in TimerGenerateMessage?

The code at lines 63-70 of [`Persona.cs`](https://github.com/gpsnmeajp/chocolatelmlite/blob/main/Persona.cs) wraps `TimerGenerateMessage` in `<system>` tags to mark the entry as a system instruction rather than user input. This distinguishes timer-generated prompts from actual conversation, allowing the LLM to recognize it as an autonomous trigger event.