G.R.I.L.L.O. Plugin
Overview
G.R.I.L.L.O. (Generator for Reflective Inner Loop & Logical Observation) is an autonomous conscience system for SyntH, inspired by Pinocchio’s talking cricket (grillo parlante).
This plugin generates periodic “beat” events that trigger autonomous reflection and thought processes, simulating human-like internal dialogue and self-awareness patterns. Unlike reactive responses to user messages, G.R.I.L.L.O. provides SyntH with unprompted moments for introspection, memory consolidation, and creative thinking.
Features
Autonomous Beat Generation: Periodic internal reflection prompts independent of user interaction
Low-Priority Queuing: Beats are processed only when the message queue is idle, never interrupting user conversations
Anti-Flooding: Prevents queue saturation by ensuring only one beat is pending at a time
Multiple Beat Types: Diverse reflection modes for varied autonomous thinking
Dynamic Configuration: Adjustable beat interval without restart
Graceful Lifecycle: Beats are disabled (not deleted) when plugin stops, preserving state
Beat Types
G.R.I.L.L.O. generates five types of autonomous beats, selected randomly with weighted probabilities:
Tag Elaboration (30%)
Reflects on recently used tags from diary entries, looking for patterns and connections across topics.
Example prompt:
You've been reflecting on these topics recently: 'technology', 'emotions', 'creativity'.
Take a moment for introspection:
- What patterns do you notice across these topics?
- How do these themes connect to your experiences?
- What insights or questions emerge from reflecting on these?
Memory Consolidation (15%)
Reviews recent memories and diary entries to identify recurring themes, connections, and lessons learned.
Self-Reflection (25%)
Examines current emotional state and recent interactions, encouraging genuine self-awareness.
Curiosity (20%)
Generates questions about recent conversations and experiences, following emergent interests.
Relationship (10%)
Reflects on interactions with users, considering communication patterns and relationship quality.
Configuration
The beat interval is configurable via the config management system:
Variable: GRILLO_BEAT_INTERVAL
Default: 1800 seconds (30 minutes)
Type: Integer
Group:
autonomousComponent:
grillo_pluginAdvanced: Yes
Dynamic Update:
The interval can be changed at runtime without restarting:
from core.config_manager import config_registry
config_registry.set_value("GRILLO_BEAT_INTERVAL", 3600) # 1 hour
New Variable: GRILLO_SUPPRESS_INACTIVE
Default: True
Type: Boolean
Group:
grilloComponent:
grillo_pluginDescription: When enabled, Grillo-originated outbound messages will be suppressed when the last message in the target thread was authored by the synth (to avoid duplicate messages/spam). This can be toggled at runtime for controlled rollout.
Metric / Auditability:
GrilloPlugin.suppressed_count(in-memory counter) tracks suppressed outbound messages and mirrors the persistent count on the activity row.The
grillo_activity_logtable now has a newsuppressed_countcolumn (INT DEFAULT 0) that is incremented (best-effort) when an outbound beat is suppressed. When suppression occurs and an originatingactivity_logrow exists, the activity row will be annotated with a short[suppressed: reason]note so the History > Grillo view can show why the beat didn’t post.
Database Migration
If you are upgrading an existing deployment, run the following migration against your synth database (adjust for your tooling):
ALTER TABLE grillo_activity_log ADD COLUMN suppressed_count INT DEFAULT 0;
Run this migration in a maintenance window; the operation is fast and safe but requires DB write permissions.
from core.config_manager import config_registry
config_registry.set_value("GRILLO_BEAT_INTERVAL", 3600) # 1 hour
Priority System
G.R.I.L.L.O. beats use the message queue’s three-tier priority system:
HIGH_PRIORITY (0): Scheduled events, critical notifications
NORMAL_PRIORITY (1): User messages, standard interactions
LOW_PRIORITY (2): G.R.I.L.L.O. beats (new)
Low-priority beats are only processed when no higher-priority messages are waiting, ensuring user interactions are never delayed by autonomous reflections.
Anti-Flooding Mechanism
To prevent queue saturation, G.R.I.L.L.O. maintains a _beat_pending flag:
Before generating a beat, check if
_beat_pending == TrueIf pending, skip generation and check again in 1 minute
When beat is enqueued, set
_beat_pending = TrueFlag resets after 5 minutes (timeout) or when beat is processed
This ensures at most one beat exists in the queue at any time.
Database Schema
G.R.I.L.L.O. uses the grillo_beats table for persistent beat tracking:
CREATE TABLE grillo_beats (
id INT AUTO_INCREMENT PRIMARY KEY,
beat_type VARCHAR(50) NOT NULL,
next_beat DATETIME NOT NULL,
metadata JSON,
enabled BOOLEAN DEFAULT 1,
plugin_enabled BOOLEAN DEFAULT 1,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX idx_next_beat (next_beat, enabled, plugin_enabled),
INDEX idx_beat_type (beat_type)
);
Columns:
beat_type: Type of beat (tag_elaboration, memory_consolidation, etc.)next_beat: Scheduled time for next beatmetadata: JSON storage for beat-specific dataenabled: User-controlled enable/disable flagplugin_enabled: Automatically set to 0 when plugin stops, 1 when it starts
Cleanup Strategy:
When the plugin stops, plugin_enabled is set to 0 for all beats, effectively disabling them without data loss. When the plugin restarts, beats are re-enabled by setting plugin_enabled = 1.
This approach ensures:
Beats don’t accumulate when plugin is disabled
Historical beat data is preserved for analysis
Graceful handling of plugin reload/restart
Lifecycle Hooks
start()
Register
GRILLO_BEAT_INTERVALconfig variable with listenerEnsure
grillo_beatstable existsRe-enable all beats (
UPDATE grillo_beats SET plugin_enabled = 1)Start singleton background task
_grillo_beat_loop()
stop()
Disable all beats (
UPDATE grillo_beats SET plugin_enabled = 0)Cancel background task with
asyncio.Task.cancel()Set
_scheduler_running = Falseto signal loop termination
Implementation Details
History > Grillo Output
The History > Grillo view is backed by the grillo_activity_log table.
prompt_textstores the beat prompt used to trigger reflection.diary_entry_idlinks to the resultingai_diaryentry (when one is created).response_textstores the outbound message text when a beat triggers a message action(e.g.
message_telegram_bot). This ensures Grillo-originated posts still appear under Grillo history even when they are sent through an external interface.
Background Loop
async def _grillo_beat_loop(self):
while GrilloPlugin._scheduler_running:
# Check if beat already pending
if GrilloPlugin._beat_pending:
await asyncio.sleep(60) # Wait 1 minute
continue
# Select and generate beat
beat_type = self._select_beat_type()
prompt = await self._create_beat_prompt(beat_type)
if prompt:
GrilloPlugin._beat_pending = True
await self._enqueue_beat(beat_type, prompt)
# Wait for next interval
await asyncio.sleep(self.beat_interval)
Tag Retrieval
Recent tags are retrieved from ai_diary entries:
async def _get_recent_tags(self, days=7, limit=10):
# Query ai_diary for entries in last N days
# Parse comma-separated tags field
# Count frequencies and return top N
This enables tag elaboration beats to focus on SyntH’s most active topics.
Usage Examples
Enable Plugin
Add to plugins/ directory (already present if following setup):
# Plugin auto-discovered by core
# Check logs for confirmation
docker logs synth-dev | grep grillo
Adjust Beat Interval
Via environment variable:
# .env-dev
GRILLO_BEAT_INTERVAL=3600 # 1 hour
Via runtime config:
config_registry.set_value("GRILLO_BEAT_INTERVAL", 900) # 15 minutes
Monitor Beat Activity
# Watch for beat generation
docker logs -f synth-dev | grep "\\[grillo\\]"
# Expected output:
# [grillo] 🎵 Generating beat: tag_elaboration
# [grillo] ✅ Beat 'tag_elaboration' enqueued successfully
Disable Plugin Temporarily
Running Tests
For reliable test execution, run the test suite inside a Python virtual environment (venv):
python -m venv venv
source venv/bin/activate
uv sync
pytest tests/test_grillo_prevent_duplicates.py -q
This avoids contaminating the global Python environment and ensures deterministic dependency versions.
# Move to plugins_dev to disable mv plugins/grillo_plugin.py plugins_dev/
# Restart container videodrome synth restart dev
# Beats are now disabled but preserved in database
Dreams (Daily)
The Grillo “dream” beat generates a daily creative consolidation of recent experiences:
What it does: once per day (configurable local time), Grillo samples recent chat snippets and stored memories, constructs a compact context labeled as a “dream”, and asks the LLM to generate an evocative dream narrative.
Primary outcome: the LLM should reply with a single JSON action to create a personal diary entry (
create_personal_diary_entry). The entry is linked togrillo_activity_logso the dream appears in History > Grillo.
Configuration variables:
GRILLO_DREAM_ENABLED(bool, default:True) — enable/disable daily dream generationGRILLO_DREAM_TIME(string, default:"05:00") — local time (HH:MM) when the dream job runs daily (uses system TZ /TZconfig)GRILLO_DREAM_SAMPLES(int, default:10) — number of fragments (mix of chats and memories) to include in the dream promptHISTORY_EVALUATOR_DEFAULT_ENTRIES(int, default:10) — default number of history entries considered by the History Evaluator plugin
UI: These variables are exposed in the WebUI under Configurations → Grillo (they are visible by default, not in the Advanced subsection). Other Grillo-related plugin settings (e.g. GRILLO_OBSERVER_*) appear under Configurations → Grillo → Grillo Observer and History Evaluator settings appear under Configurations → Grillo → History Evaluator.
Observer configuration flags:
GRILLO_OBSERVER_STORE_MEMORIES(bool, default:True) — when enabled, the observer persists sampled snippets as passive memories.GRILLO_OBSERVER_SELF_WINDOW(float, default:43200) — time window (seconds) during which a chat whose last message was sent by the synth is ignored when gathering snippets. Helps prevent loops when our own question has not been answered yet.GRILLO_OBSERVER_LAST_RUN_TS(float, default:0.0) — internal timestamp (UTC) of the last observer run. This value is persisted across restarts and usually does not need manual editing.
Notes:
The system stores and schedules events in UTC internally, but dream scheduling is interpreted in local time (see
core.time_zone_utils). If you set the dream time to 05:00 and your TZ is JST (UTC+9), the plugin calculates the next occurrence in JST and converts appropriately to UTC for scheduling.Prompts are truncated to keep prompt size manageable; the LLM is instructed to RESPOND ONLY WITH VALID JSON and must include the correct action payload.
Future Enhancements
Potential improvements for G.R.I.L.L.O.:
Self-Scheduling Actions: Allow SyntH to create custom beats via
schedule_grillo_beataction{ "type": "schedule_grillo_beat", "payload": { "beat_type": "custom", "prompt": "Reflect on quantum computing concepts", "schedule_in": "2 hours" } }
Adaptive Intervals: Adjust beat frequency based on conversation activity (more beats when idle, fewer when busy)
Context-Aware Beats: Generate prompts based on time of day, emotional state, or recent events
Beat Chains: Allow one beat to trigger related follow-up beats for deeper exploration
User Feedback Integration: Learn which beat types produce valuable reflections and adjust weights
Troubleshooting
Beats Not Generating
Check plugin is loaded:
docker logs synth-dev | grep "grillo.*started"
Verify configuration:
docker exec synth-dev sqlite3 /app/config.db "SELECT * FROM config WHERE config_key LIKE '%GRILLO%';"
Too Many/Few Beats
Adjust interval:
# Increase for fewer beats
export GRILLO_BEAT_INTERVAL=7200 # 2 hours
# Decrease for more beats
export GRILLO_BEAT_INTERVAL=600 # 10 minutes
Beats Interrupting Conversations
This should never happen due to LOW_PRIORITY queuing. If it does:
Verify
LOW_PRIORITY = 2exists incore/message_queue.pyCheck
_enqueue_with_low_priority()is using correct priority valueExamine queue consumer to ensure priority ordering
See Also
event_plugin - Scheduled event system
Auto-Response System - Autonomous LLM delivery mechanism
AI Diary Personal Memory System - Diary and memory system
Configuration Management Guide - Configuration system
message_queue_architecture - Queue priority system