Two-Phase Initialization Implementation
Date: 2025-10-11
Problem
After core refactoring, the telegram_bot interface was not appearing in the WebUI. Investigation revealed a fundamental timing issue:
Old pattern: Components created instances at module import time
New pattern: Configuration variables loaded from database
Conflict: Variables registered during import had default values (not DB values)
Symptom
BOTFATHER_TOKEN = config_registry.get_var("BOTFATHER_TOKEN", None, ...) # Returns None during import
telegram_interface = TelegramInterface() # Created immediately with None token
Result: Interface always disabled because token was None.
Solution: Two-Phase Initialization
Phase 1: Discovery (Import Time)
Modules are imported
Variables registered with
config_registry.get_var()NO instances created
Purpose: Tell core “these variables exist and should be loaded from DB”
Phase 2: Initialization (Post-DB-Load)
All variables loaded from database
All listeners notified with DB values
Core calls
initialize_interface()/initialize_plugin()Instances created with correct DB values
Implementation
Changes to telegram_bot.py
1. Variable Registration (Phase 1)
# At module level - registers variables but doesn't create instances
BOTFATHER_TOKEN = config_registry.get_var(
"BOTFATHER_TOKEN",
None,
label="Telegram Bot Token",
description="Bot token from @BotFather",
group="interface",
component="telegram_bot",
sensitive=True,
)
TRAINER_IDS = config_registry.get_var(
"TRAINER_IDS",
"",
label="Trainer IDs",
description="Comma-separated list of trainer IDs (format: interface:user_id)",
group="core",
component="trainer",
)
# Global instance variable
telegram_interface = None
2. Lazy Initialization (Phase 2)
def initialize_interface():
"""
Initialize the Telegram interface after config has been loaded from DB.
Called by core after database values are loaded.
Supports reload: if instance exists, shuts it down first.
"""
global telegram_interface
# Reload support: cleanup existing instance
if telegram_interface is not None:
shutdown_interface()
log_info("[telegram_bot] Creating Telegram interface instance...")
telegram_interface = TelegramInterface(None)
register_interface("telegram_bot", telegram_interface)
return telegram_interface
3. Lifecycle Management
def shutdown_interface():
"""Cleanup before reload or shutdown."""
global telegram_interface
if telegram_interface is None:
return
# Cleanup resources
# (e.g., close connections, stop threads)
# Unregister from core
from core.core_initializer import INTERFACE_REGISTRY
if "telegram_bot" in INTERFACE_REGISTRY:
del INTERFACE_REGISTRY["telegram_bot"]
telegram_interface = None
def reload_interface():
"""Reload with updated configuration."""
return initialize_interface()
4. Instance Changes
class TelegramInterface:
def __init__(self, bot_token_ignored):
# Read variables - NOW they have correct values from DB
self.bot_token = BOTFATHER_TOKEN
self.trainer_id = _parse_trainer_id_from_config()
# Check configuration
self.is_enabled = bool(self.bot_token)
self.disabled_reason = None if self.is_enabled else "BOTFATHER_TOKEN not configured"
if not self.is_enabled:
log_warning(f"[telegram_interface] Loaded in disabled state: {self.disabled_reason}")
Changes to core/core_initializer.py
Added Interface Instance Initialization
def _initialize_interface_instances(self):
"""
Phase 2: Initialize interface instances after config is loaded.
Calls initialize_interface() on modules that implement it.
"""
log_debug("[core_initializer] Starting interface instance initialization...")
for module_name in self._interface_modules:
module = sys.modules.get(module_name)
if module and hasattr(module, 'initialize_interface'):
try:
log_debug(f"[core_initializer] Calling initialize_interface() for {module_name}")
interface = module.initialize_interface()
log_debug(f"[core_initializer] Successfully initialized {module_name}")
except Exception as e:
log_error(f"[core_initializer] Failed to initialize {module_name}: {e}")
else:
log_debug(f"[core_initializer] Module {module_name} has no initialize_interface function")
Updated Initialization Sequence
async def initialize_all(self):
# 1. Initialize base systems
await self._initialize_registries()
self._load_llm_engines()
self._load_plugins()
# 2. PHASE 1: Import interfaces (registers variables)
self._discover_interfaces()
# 3. Load config from database
await config_registry.load_all_from_db()
config_registry.notify_all_listeners()
log_info("✅ All config listeners notified")
# 4. PHASE 2: Initialize interface instances
self._initialize_interface_instances()
log_info("✅ Interface instances initialized")
# 5. Start interfaces
await self._start_interfaces()
Verification
Log Sequence (Correct Order)
[01:33:10] Successfully imported: telegram_bot # Phase 1
[01:33:10] ✓ Loaded 'BOTFATHER_TOKEN' from DB: 7934437... # DB Load
[01:33:10] ✓ Loaded 'TRAINER_IDS' from DB: telegram_bot:31321637 # DB Load
[01:33:10] ✓ Notified 18 listener(s) with updated config values # Notify
[01:33:10] Calling initialize_interface() for {module_name} # Phase 2
[01:33:10] Creating Telegram interface instance... # Phase 2
[01:33:10] Interface enabled # Phase 2
[01:33:10] TelegramInterface instance initialized # Phase 2
[01:33:10] 🔌 Interface loaded: telegram_bot # Success
[01:33:10] 📡 Active Interfaces: ... telegram_bot # Success
WebUI Verification
$ curl -s http://localhost:9009/api/components | python3 -m json.tool | grep -A 5 telegram
Output:
{
"name": "telegram_bot",
"display_name": "Telegram Bot",
"description": "Interface wrapper providing a standard send_message method for Telegram.",
"actions": [
{
"name": "message_telegram_bot",
...
},
{
"name": "audio_telegram_bot",
...
}
],
"status": "unknown",
"details": "",
"error": ""
}
✅ telegram_bot now appears in WebUI with all actions registered correctly
Benefits
Database Configuration Works: Variables loaded from DB before instances created
Environment Override Still Works: Env vars have higher priority than DB
Hot Reload Supported: Components can be reloaded when config changes
Clean Architecture: Core controls initialization order, not components
Consistency: All components follow the same pattern
WebUI Integration: Configuration automatically appears in WebUI
Next Steps
Apply Pattern to Other Interfaces
The following interfaces should be migrated to the two-phase pattern:
[ ]
discord_interface.py[ ]
matrix_interface.py[ ]
ollama_compat_server.py
Migration checklist for each:
Move instance creation to
initialize_interface()Add
shutdown_interface()for cleanupAdd
reload_interface()for hot reloadKeep variable registration at module level
Export lifecycle functions in
__all__Test with database configuration
Documentation
See component_pattern.rst for the complete developer guide on implementing components with two-phase initialization.