Architecture Overview
The Synthetic Heart is a highly modular AI persona system designed for extensibility and flexibility. The architecture separates concerns into distinct layers, allowing independent development and swapping of components without modifying the core system.
Core Principles
- Modularity and Extensibility
Components (Cortex engines, plugins, and interfaces) are not hardcoded in the core. Instead, they are dynamically discovered and loaded at runtime through auto-registration mechanisms. This ensures that new functionality can be added by simply placing compatible modules in the appropriate directories.
- Message-Driven Architecture
All interactions flow through a centralized message chain that handles JSON action parsing, validation, and execution. Components communicate through well-defined interfaces rather than direct coupling.
- Registry-Based Component Management
Components register themselves with central registries, providing metadata about their capabilities. The core system then orchestrates interactions based on this self-reported information.
System Components
coreThe foundational layer containing:
Message Chain (
message_chain.py): Central orchestrator for incoming messages, JSON extraction, and action execution flow.Action Parser (
action_parser.py): Executes validated actions from parsed JSON, routing them to appropriate component handlers.Core Initializer (
core_initializer.py): Auto-discovers and loads all components fromplugins/,cortex/, andinterface/directories.Registries: Centralized management for Cortex engines, interfaces, plugins, and validation rules.
Transport Layer: Handles communication between components and interfaces.
Interface Path System: Unified hierarchical addressing system using
interface_pathformat (e.g., “telegram_bot/chat_id/thread_id”) for consistent conversation identification across all platforms. See Interface Path System for detailed documentation.
interfaceChat platform integrations (Telegram, Discord, Reddit, etc.). Each interface:
Inherits from
AIPluginBaseor implements compatible interfacesRegisters supported actions (e.g., sending messages, media)
Handles platform-specific message formatting and rate limiting
Provides trainer ID validation for security
cortex/Runtime engine implementations (OpenAI, Google Gemini, manual input, etc.). Each engine:
Extends
AIPluginBasefor consistent interfaceHandles prompt construction and response generation
Manages model-specific limits and capabilities
Supports both text and multimodal interactions
Cortex engines expose configuration variables (API keys, endpoints, model selectors) through the shared settings registry. These variables can be edited in the Web UI even when the engine is not active. When a required variable is missing, the engine may load in a degraded state and is surfaced as failed in the Components view so operators can see it needs attention. Variables marked with
needs_component_reloadautomatically trigger a reload of the owning Cortex engine after an update, allowing API keys to be applied without restarting the whole system.
Prompt Assembly
Prompt assembly is now centered on the typed
core.prompt_request.PromptRequest model.
core.prompt_engine.build_prompt_request()is the canonical builder.build_json_prompt()remains as a deprecated alias for compatibility.- The builder still returns a compatibility dict for older callers, but it now
attaches the typed request under
__prompt_request.
- Migrated engines render the typed request natively through
core.prompt_renderersinstead of serializing the full prompt as one indented JSON blob.
This split lets the runtime preserve conversation turns, keep system instructions stable for caching, and handle chat, grillo, delivery, and live prompt modes without forcing every engine down the same text-only path.
pluginsAction providers that extend synth’s capabilities (terminal access, weather, file operations, etc.). Each plugin:
Implements action schemas with required/optional fields
Provides prompt instructions for LLM integration
Handles validation and execution of specific actions
Can be pure action providers or full AI plugins
Advanced Components
Event HandlerAllows the synth to schedule and manage events for future execution. Events can be spontaneous or user-requested, enabling proactive behaviors like sending morning greetings, weather warnings, or checking in on users. This creates a sense of initiative and care.
Thinking Engine (G.R.I.L.L.O.)A low-priority, configurable background process that generates internal thoughts and proposals for the synth. It picks up on conversations or ideas, prompting autonomous actions. For example, if users discuss AI art limitations, the synth might research human art concepts or artist communities. This demonstrates emerging “will” and self-directed behavior, though full implementation depends on available plugins like web search.
Message Flow
The following diagram shows the complete message processing pipeline:
digraph message_flow { rankdir=TB; node [shape=box, style=rounded]; User [label="User Message"]; Interface [label="Interface\n(Telegram/Discord/etc.)"]; MessageChain [label="Message Chain"]; JSONExtract [label="JSON Extraction"]; ActionParser [label="Action Parser"]; Component [label="Component\n(Plugin/Cortex)"]; Response [label="Response"]; User -> Interface; Interface -> MessageChain; MessageChain -> JSONExtract; JSONExtract -> ActionParser [label="Valid JSON"]; JSONExtract -> Corrector [label="Invalid JSON"]; Corrector -> MessageChain [label="Retry"]; ActionParser -> Component; Component -> Response; Response -> Interface; Response -> User; Corrector [label="Corrector Middleware\n(LLM-assisted)"]; }Message Queue System
The Synthetic Heart uses an asyncio.PriorityQueue for processing messages in the correct order. Messages are prioritized to ensure high-priority events (like scheduled messages) are processed before regular chat messages.
Priority Levels: - HIGH_PRIORITY (0): Scheduled events, urgent notifications - NORMAL_PRIORITY (1): Regular chat messages
PriorityQueue Fix: To prevent TypeError when comparing messages with identical priorities, the queue uses a monotonic counter as tiebreaker. Each message is stored as (priority, counter, item) instead of (priority, item), ensuring consistent ordering even when priorities are equal.
Consumer Loop: A dedicated async task continuously processes queued messages, ensuring no message loss and proper serialization of operations.
Database Connection Pool
The system maintains a single aiomysql connection pool with carefully tuned parameters:
Max Size: 5 connections (reduced from 8 to prevent bio_manager timeouts)
Min Size: 1 connection (keeps one connection alive)
Timeout: 10 seconds for connection acquisition
Bio Manager Optimization: The bio_manager plugin caches table initialization and uses async database operations to prevent TimeoutError when retrieving user profiles during prompt injection.
Recent Fixes
PriorityQueue TypeError Fix (November 2025):
The message queue was experiencing TypeError: '<' not supported between instances of 'dict' and 'dict' when processing messages with identical priorities.
Root Cause: Python’s heapq compares tuple elements when priorities are equal, but dict objects cannot be compared with <.
Solution: Added monotonic counter as tiebreaker in queue tuples: .. code-block:: python
# Before: (priority, item) - fails when priority equal # After: (priority, counter, item) - always comparable
_counter += 1 await _queue.put((priority, _counter, item))
Files Modified:
- core/message_queue.py: Added global counter and updated all put() calls
Impact: Eliminates queue crashes and ensures consistent message processing order.
Detailed Flow:
Message Reception: Interface receives message from user and forwards to message chain
JSON Detection: Message chain attempts to extract JSON actions from the text
Validation & Correction: If JSON is invalid, corrector middleware queries the active Cortex engine to fix it
Action Execution: Validated actions are parsed and routed to appropriate components
Response Generation: Components execute actions and generate responses
Output Delivery: Responses are sent back through the originating interface
Component Auto-Discovery
Components are automatically discovered through a recursive import system:
The core initializer scans
plugins/,cortex/, andinterface/directoriesEach Python file is imported and checked for a
PLUGIN_CLASSattributeCompatible classes are instantiated and registered with appropriate registries
Components self-report their capabilities through standardized methods
No manual registration or configuration files required
This approach ensures that adding new functionality requires only: 1. Creating a compatible class in the appropriate directory 2. Implementing required interface methods 3. Restarting the application
Security and Validation
Trainer ID Validation: Interfaces validate that commands come from authorized trainers
Action Validation: All actions are validated against component schemas before execution
Rate Limiting: Built-in rate limiting prevents abuse across all components
Error Handling: Comprehensive error handling with user-friendly notifications
The architecture’s modular design ensures that security policies can be consistently applied across all components without code duplication.
Unified Lane & Unified History
Synthetic Heart supports optional Unified Lane and Unified History behaviors to improve continuity across interfaces and sessions.
- Unified Lane (Context Linking)
Allows multiple interface paths to map to a single shared context lane. This is useful when you want WebUI, Discord, and other interfaces to share a common conversation history.
Configure the mapping via
CONTEXT_LINK_MAP(JSON), for example:{ "synth_webui/SESSION_UUID": "lane_Trainer_Main", "discord_bot/123456/0": "lane_Trainer_Main", "123456": "lane_Trainer_Main" }
By default the WebUI uses a single persistent session id that is stored in
backups/webui_session_id.txt. This ensures history and window state survive container restarts. An advanced, experimental optionMULTI_SESSIONcan be toggled totrue; when enabled each browser connection gets its own independent session. This mode is unstable and should only be used for testing.The resolver checks for exact path matches first and then tries a user-id match using the second path segment (
interface/chat_id/...).- Unified History
When
UNIFIED_HISTORYis enabled, the current chat history is built by merging global DB history with in-memory chat logs across all interfaces. This creates a shared “brain” for the synth. Disable this flag if you need strict isolation between users or channels.Changed in version 1.0: Unified history entries coming from other chats are now prefixed with
[from <interface_path>]to make it explicit they are not part of the current conversation.