Interfaces

Interfaces provide the communication layer between synth and external platforms. Like plugins and Cortex engines, interfaces are completely modular and automatically discovered at runtime. This ensures that platform integrations are decoupled from the core system and can be developed independently.

Interface Architecture

All interfaces follow a consistent architecture:

  • Auto-Discovery: Interfaces are automatically found in the interface/ directory

  • Standard Interface: Interfaces extend AIPluginBase or implement compatible methods

  • Action Providers: Interfaces can provide actions (sending messages, media, etc.)

  • Security Integration: Built-in trainer ID validation and rate limiting

  • Platform Abstraction: Unified message format across different platforms

Available Interfaces

Stable Interfaces (in interface/ directory):

  • discord_interface – Discord bot integration. Requires DISCORD_BOT_TOKEN.

  • fluxer_interface – Fluxer bot integration (self-hostable, Discord-compatible chat) via a native aiohttp REST + WebSocket gateway client. Requires FLUXER_TOKEN. See Fluxer Interface.

  • matrix_interface – Matrix chat bridge powered by matrix-nio. Requires homeserver credentials and tokenizer (password or access token).

  • openai_api_server – OpenAI-compatible REST bridge that lets external clients talk to SyntH as if it were an OpenAI endpoint. It also implements the legacy /api/generate · /api/chat · /api/tags routes, so tools written for a local Ollama daemon work unchanged.

  • telegram_bot – Telegram bot interface with media support. Requires BOTFATHER_TOKEN and trainer ID.

Development Interfaces (in interface_dev/ directory):

  • cli – Local command-line interface for direct interaction (no configuration).

  • reddit_interface – Asynchronous Reddit client for posts, comments, and DMs. Requires Reddit API credentials.

  • telethon_userbot – Advanced Telegram userbot using Telethon. Requires API_ID, API_HASH, and session.

  • webui – FastAPI-based web interface for browser access. Configurable host/port.

  • x_interface – Experimental X (Twitter) integration with timeline features. Requires X_USERNAME.

Discord Interface

The Discord interface provides full bot integration:

Setup Steps:

  1. Create an application at Discord Developer Portal

  2. Add a bot user and copy the token

  3. Enable required intents (Message Content Intent, etc.)

  4. Generate invite URL with bot scope and permissions

  5. Set DISCORD_BOT_TOKEN in environment variables

  6. Start synth - the interface loads automatically

Features:

  • Real-time message handling

  • Media upload/download support

  • Thread and channel management

  • Role and permission integration

Fluxer Interface

Fluxer is a self-hostable, Discord-compatible chat platform. The interface ships a native Python client (aiohttp REST + WebSocket gateway) — no .NET runtime or discord.py dependency — so it runs inside the slim container image.

Setup Steps:

  1. Create a bot account on your Fluxer server and copy its token

  2. Set FLUXER_TOKEN (the ``Bot `` prefix is added automatically if omitted)

  3. (Self-host only) override FLUXER_API_BASE_URL / FLUXER_GATEWAY_URL / FLUXER_API_VERSION to point at your deployment

  4. Start synth — the interface loads automatically

Features:

  • Real-time message handling over the gateway WebSocket with heartbeat, automatic reconnect, and session resume

  • Text messages in/out and generic file sending (send_file_fluxer_bot)

  • Fully configurable endpoints for self-hosted instances

  • Hot reload on token/endpoint changes (no restart)

See Fluxer Interface for the full configuration reference.

Telegram Bot Interface

The Telegram bot interface offers comprehensive Telegram integration:

Configuration:

BOTFATHER_TOKEN=your_bot_token
TRAINER_IDS=telegram_bot:123456789

Features:

  • Message threading support

  • Media handling (photos, documents, voice)

  • Inline keyboard and callback support

  • Group and private chat handling

  • Trainer ID security validation

  • Wake/Sleep attention mode (commands /wake /awake /sleep /status)
    • Default: awake (listens to all messages in the chat)

    • /sleep: switches to mention/reply-only handling for that chat

    • /wake / /awake: restores awake behavior

    • /status: shows the current mode

    • CHAT_SLEEP_COMMANDS / CHAT_WAKE_COMMANDS: configurable comma-separated phrases that can act as chat-level sleep/wake triggers (set via WebUI).

Reddit Interface

The Reddit interface enables social media integration:

Required Credentials:

REDDIT_CLIENT_ID=your_client_id
REDDIT_CLIENT_SECRET=your_client_secret
REDDIT_USERNAME=your_username
REDDIT_PASSWORD=your_password
REDDIT_USER_AGENT=synthFreedomProject/1.0

Capabilities:

  • Post creation and commenting

  • Direct message handling

  • Subreddit monitoring

  • User interaction tracking

Web UI Interface

The web interface provides browser-based access:

Configuration:

SYNTH_WEBUI_HOST=0.0.0.0
SYNTH_WEBUI_HTTP_PORT=8001
SYNTH_WEBUI_HTTPS_PORT=8000

See also: Compose / Environment Variables for a complete list of Compose variables, defaults and recommendations.

Features:

  • Modern web interface

  • Real-time chat updates

  • File upload support

  • Responsive design

Extended API Endpoints

The Web UI exposes additional endpoints used by external integrations such as Mate-Engine:

  • GET /api/animation_state – Current centralized animation state

  • POST /api/animation_state – Request a centralized animation state change. Body: {state, session_id?, loop?, context_id?, source?}

  • POST /api/animations/upload – Temporary animation upload (FBX/VRMA)

  • GET /api/animations/uploads – List temporary animation uploads

  • DELETE /api/animations/uploads/{upload_id} – Remove a temporary upload

  • POST /api/animations/promote – Promote a temporary upload into a skin

  • GET /api/about – About summary payload (uptime, sessions, system info)

  • GET /api/prompt_override – Interface prompt injection/override hints

  • POST /api/integrations/messages – Generic integration message endpoint. Body: {source, type, payload, metadata}. type=chat is forwarded to the message chain; other types are stored for retrieval.

  • GET /api/integrations/outbox?source=<source> – Retrieve queued messages for a given integration source (clears the queue on read). Use source=mate for Mate Engine clients.

Plugins

A convenience plugin, plugins/mate_engine.py, is provided as an optional integration helper. It exposes actions that LLMs can call and implements policy-sensitive behaviour such as promotion of temporary uploads:

  • send_mate_message – Enqueue a message to the Mate outbox (source=”mate”).

  • promote_upload – Promote a temporary upload into a skin (requires SYNTH_MATEENGINE_PROMOTE_ENABLED=1 to permit promotion).

Set SYNTH_MATEENGINE_PROMOTE_ENABLED=1 to enable promotion from uploads into person skins (default: disabled).

Mate Engine Flags

  • SYNTH_MATEENGINE_UPLOAD_TTL_DAYS – Days before temporary uploads are removed (default: 7)

  • SYNTH_MATEENGINE_UPLOAD_CLEANUP_INTERVAL_S – Cleanup interval in seconds (default: 3600)

  • SYNTH_MATEENGINE_PROMOTE_ENABLED – Set to 1 to allow promotion of uploads

OpenAI-Compatible Server

The OpenAI-compatible API server exposes Synthetic Heart through a standard OpenAI-style HTTP surface. For backward compatibility it also mirrors the legacy REST API used by ollama, so any tool that can talk to an OpenAI endpoint—or to a local Ollama daemon—can point at SyntH and receive the responses generated by your active persona.

What it does

  • Implements /api/generate and /api/chat with streaming NDJSON output.

  • Mirrors the legacy /api/tags endpoint so discovery requests return a synthetic model catalogue.

  • Translates incoming prompts into the synth message chain, letting the currently loaded Cortex engine drive the reply.

Configuration

OLLAMA_HOST=0.0.0.0          # Bind address for the compatibility server
OPENAI_API_SERVER_PORT=11435 # Default Ollama port; update if you already run a native instance
OLLAMA_DEFAULT_MODEL=SyntH   # Name reported to clients when no model is specified
OLLAMA_DEFAULT_MODEL_DISPLAY="Synthetic Heart"  # Friendly label in /api/tags
OLLAMA_MAX_HISTORY=20        # Conversation turns preserved between requests
OLLAMA_STREAM_TIMEOUT=10.0   # Seconds to wait between streamed chunks before timing out
OLLAMA_COMPLETION_TIMEOUT=0  # Optional deadline for non-streaming calls (0 disables)

Usage

  1. Start SyntH with the interface enabled (it registers automatically when the interface/openai_api_server/ package is present).

  2. Point an OpenAI-compatible (or Ollama) client at your synth instance:

    export OLLAMA_HOST=http://<synth-host>:11435
    # any OpenAI-compatible or Ollama client can now list and chat with SyntH
    
  3. Any third-party application that supports the OpenAI REST API (or the legacy Ollama REST API) can reuse the same base URL. This makes it simple to integrate synth with dashboards, IDEs, or automation frameworks.

Because the server streams responses as soon as the persona produces them, end users get the familiar chat experience while benefiting from SyntH’s plugin ecosystem, persona memory, and dispatcher logic.

Interface Registration System

Interfaces are automatically discovered and integrated:

  1. Directory Scanning: Core scans interface/ for Python modules

  2. Class Discovery: Files checked for INTERFACE_CLASS or PLUGIN_CLASS

  3. Registration: Interfaces register with the interface registry

  4. Capability Indexing: Supported actions and features are cataloged

  5. Security Setup: Trainer IDs configured from environment variables

Developing Interfaces

Creating a new interface requires implementing the interface contract:

from core.ai_plugin_base import AIPluginBase
from core.core_initializer import register_interface
from core.interfaces_registry import get_interface_registry

class MyInterface(AIPluginBase):
    # Declarative "must-have" configuration. The loader checks these keys
    # before activating the interface (see "Required Configuration" below).
    required_config_vars = ["MYINTERFACE_TOKEN"]

    @staticmethod
    def get_interface_id() -> str:
        """Return unique interface identifier."""
        return "myinterface"

    @staticmethod
    def get_supported_action_types() -> list[str]:
        """Return action types this interface supports."""
        return ["message"]

    @staticmethod
    def get_supported_actions() -> dict:
        """Return action schemas using the optimized format."""
        return {
            "message_myinterface": {
                "schema": {
                    "type": "object",
                    "properties": {
                        "text": {
                            "type": "string",
                            "description": "Message content to send"
                        },
                        "interface_path": {
                            "type": "string",
                            "description": "Hierarchical interface path (e.g., 'telegram_bot/chat_id/thread_id')"
                        },
                        "media": {
                            "type": "string",
                            "description": "Optional media attachment URL"
                        }
                    },
                    "required": ["text", "target"]
                },
                "brief": "Send a message via MyInterface",
                "examples": {
                    "description": "Send a message through MyInterface with optional media attachment.",
                    "instructions": {
                        "when_to_use": "Use to communicate through MyInterface channels or direct messages.",
                        "common_pitfalls": [
                            "Ensure target exists and is accessible",
                            "Media URLs must be publicly accessible"
                        ]
                    },
                    "examples": [
                        {
                            "scenario": "Send text message",
                            "payload": {
                                "text": "Hello world!",
                                "interface_path": "myinterface/channel1"
                            }
                        },
                        {
                            "scenario": "Send message with media",
                            "payload": {
                                "text": "Check this out",
                                "interface_path": "myinterface/user123",
                                "media": "https://example.com/image.jpg"
                            }
                        }
                    ]
                }
            }
        }

    def get_prompt_instructions(self, action_name: str) -> dict:
        """Provide LLM instructions for interface actions."""
        if action_name == "message_myinterface":
            return {
                "description": "Send a message through MyInterface.",
                "payload": {
                    "text": {"type": "string", "description": "Message content"},
                    "interface_path": {"type": "string", "description": "Hierarchical interface path (e.g., 'myinterface/channel1')"},
                    "media": {"type": "string", "description": "Optional media URL"}
                }
            }
        return {}

    def validate_payload(self, action_type: str, payload: dict) -> list[str]:
        """Validate action payloads."""
        errors = []
        if action_type == "message_myinterface":
            if "text" not in payload:
                errors.append("payload.text is required")
            if "interface_path" not in payload:
                errors.append("payload.interface_path is required")
        return errors

    async def start(self):
        """Initialize the interface."""
        # Register with core systems
        register_interface("myinterface", self)
        core_initializer.register_interface("myinterface")

        # Start your platform connection here
        await self.connect_to_platform()

    async def connect_to_platform(self):
        """Platform-specific connection logic."""
        # Implement platform connection
        pass

    async def handle_incoming_message(self, bot, message, prompt):
        """Handle incoming messages (if this interface also acts as LLM)."""
        # Optional: if interface can also generate responses
        pass

# Required: Export the interface class
INTERFACE_CLASS = MyInterface

Required Configuration (required_config_vars)

Most interfaces are useless without at least one secret (a bot token, an access token, credentials, …). Rather than letting an interface half-start and flood the LLM prompt with actions it can never actually perform, an interface simply declares which config keys are indispensable and lets the loader enforce them. The interface performs no gating itself — it only declares intent.

How to declare

Add a required_config_vars class attribute — an iterable of config-registry keys. Each entry can be one of two forms:

  • a plain string — that key must be present (AND semantics), or

  • a tuple / list — an OR group: at least one member must be present.

class MyInterface(AIPluginBase):
    # AND: every plain-string key is mandatory.
    # OR: the tuple is satisfied if *any* of its members is set.
    required_config_vars = [
        "MYINTERFACE_USER",                        # required outright
        ("MYINTERFACE_PASSWORD", "MYINTERFACE_TOKEN"),  # need one of the two
    ]

How the loader enforces it

At registration time core_initializer calls _missing_required_config_vars(interface_instance), which resolves every declared key through config_registry.get_value(...). A value that is None or an empty/whitespace string counts as missing. Then:

  • All present → the interface is loaded normally and its actions become available to the LLM.

  • Something missing → the interface is not loaded: it is not added to active_interfaces and none of its actions are registered, so they never reach the prompt. The component is still tracked and marked FAILED with a reason like Missing required configuration: MYINTERFACE_USER, MYINTERFACE_PASSWORD or MYINTERFACE_TOKEN — this shows up as a red LED in the WebUI Interfaces section (the interface stays visible, it is not hidden).

Because the gate runs in the loader, the interface constructor is free to behave as usual (for example is_enabled may still default to True) — the must-have check is orthogonal to the interface’s own enable/disable logic. The action parser additionally filters both action-collection passes by core_initializer.active_interfaces, so an interface that failed the gate can never leak schemas into the prompt.

Real examples in the tree

Interface

required_config_vars

Telegram (interface/telegram_bot.py)

["BOTFATHER_TOKEN"]

Discord (interface/discord_interface.py)

["DISCORD_BOT_TOKEN"]

Fluxer (interface/fluxer_interface.py)

["FLUXER_TOKEN"]

Matrix (interface/matrix_interface.py)

["MATRIX_USER", ("MATRIX_PASSWORD", "MATRIX_ACCESS_TOKEN")]

Matrix shows the mixed AND + OR case: it needs its MXID (MATRIX_USER) and either a password or an access token to log in. The homeserver key is not listed because it ships with a non-empty default and is therefore always “present”.

Guidelines

  • Declare only what is genuinely indispensable to operate — do not over-gate. Keys that have a sensible non-empty default do not belong here (they always resolve as present).

  • Prefer an OR group over duplicating logic when a platform accepts alternative credentials (password or token).

  • Omit the attribute entirely (or set it to an empty list) for interfaces that need no secrets — e.g. the local CLI. Such interfaces are never gated.

Interface Actions

Interfaces can provide actions that LLMs can invoke:

Message Sending:

{
  "type": "message_telegram_bot",
  "payload": {
    "text": "Hello from synth!",
    "interface_path": "telegram_bot/123456789/2"
  }
}

Media Handling:

{
  "type": "send_media_discord",
  "payload": {
    "file_url": "https://example.com/image.png",
    "interface_path": "discord_interface/987654321/123"
  }
}

Security and Validation

Trainer ID Validation:

Interfaces validate that sensitive operations come from authorized users:

TRAINER_IDS=telegram_bot:123456789,discord_interface:987654321

Rate Limiting:

Built-in rate limiting prevents abuse:

  • Per-user rate limits

  • Burst protection

  • Platform-specific constraints

Input Validation:

All inputs are validated before processing:

  • Payload schema validation

  • Type checking

  • Content filtering

Best Practices

Action Schema Design

Use the new three-tier action format for optimal prompt efficiency. See Action Schema Format for details.

Error Handling

Implement comprehensive error handling with user feedback.

Async Operations

Use async methods for all I/O operations.

Security First

Always validate trainer permissions for sensitive actions.

Platform Limits

Respect platform rate limits and content policies.

Documentation

Provide clear action schemas and examples in the examples tier.

For complete implementations, examine interface/telegram_bot.py or interface/discord_interface.py in the repository.