Timer Functionality for Autonomous Speech in ChocolateLMLite: Complete Implementation Guide

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 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 and consumed by the periodic task runner.

Global Toggle and Limits in FileManager.cs

  • EnableTimer (lines 178-188): The master switch stored in 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 (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:
var nextGeneratedAt = lastGeneratedAt.AddMinutes(activePersonaSettings.TimerCycleMinutes);

This logic appears at lines 34-40 of 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):

  • 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:
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.

YAML Configuration (Manual)

Edit data/settings.yaml for global settings and data/persona_1.yaml (or your specific persona file) for interval definitions:


# 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 (lines 151-164):

  1. Navigate to SettingsGeneral.
  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:

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:

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:

  • Global settings (settings.yaml): Contains EnableTimerGenerate, TimerGenerateLimitMax, and TimerGenerateMessage.
  • Persona files (persona_*.yaml): Contains TimerCycleMinutes and webhook settings.

Summary

  • EnableTimerGenerate in settings.yaml acts as the master switch for autonomous speech.
  • TimerCycleMinutes controls the interval between generations for each persona.
  • PerformPeriodicTasks in 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.

Frequently Asked Questions

How do I completely disable the autonomous speech timer?

Set EnableTimerGenerate: false in 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 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.

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 →