How to Add a New Platform Interface to the Heurist Agent Framework: Slack, WhatsApp, and Custom Integrations

To add a new platform interface to the Heurist Agent Framework, create a wrapper class that stores a reference to CoreAgent in _parent, registers itself via register_interface(), implements the async def send_message(self, chat_id: str, message: str, image_url: str | None = None) coroutine, and forwards inbound messages to self.handle_message().

The Heurist Agent Framework decouples reasoning logic from communication channels, allowing you to add a new platform interface such as Slack or WhatsApp without modifying the core AI pipeline. By treating every external service as a thin translation layer, the framework enables agents to respond uniformly across Telegram, Discord, Twitter, and any custom platform you implement.

Architecture of a Platform Interface

A platform interface in the Heurist Agent Framework acts as a bridge between external messaging services and the CoreAgent located in agents/core_agent.py. Unlike traditional inheritance patterns, platform interfaces use composition to reuse logic.

The interface stores a reference to the core agent in the private attribute _parent and delegates missing attributes via __getattr__ and __setattr__. This design lets the interface call self.handle_message() and self.register_interface() as if it were the core agent itself, while only implementing platform-specific send/receive logic.

According to the source code in agents/base_agent.py, every interface must satisfy three contractual obligations:

  1. Link to CoreAgent – Store the core agent reference in _parent and call CoreAgent.register_interface(name, self) during initialization.
  2. Implement send_message – Provide an async method with the exact signature async def send_message(self, chat_id: str, message: str, image_url: str | None = None) that the core agent invokes when generating replies.
  3. Forward inbound events – Capture platform-specific events and translate them into calls to self.handle_message(message=..., source_interface=..., chat_id=...).

Step-by-Step Guide to Adding a Slack Interface

Follow these concrete steps to add a new platform interface for Slack. The same pattern applies to WhatsApp, Microsoft Teams, or any other service.

1. Create the Interface File

Add a new file at interfaces/slack.py. Use interfaces/telegram.py as a reference template, as it demonstrates the proper parent-linking pattern and event handling structure.

2. Install Platform Dependencies

Add the Slack SDK to your environment. The framework uses uv for dependency management:

uv add slack_sdk

Document the new requirement in pyproject.toml to ensure reproducible builds.

3. Configure Environment Variables

Define authentication variables in .env.example and your runtime environment:

SLACK_BOT_TOKEN=xoxb-your-bot-token
SLACK_SIGNING_SECRET=your-signing-secret

The framework follows a strict convention where each interface reads its credentials from environment variables, matching the pattern used by TELEGRAM_API_TOKEN and DISCORD_TOKEN in existing implementations.

4. Implement the SlackAgent Class

Create the class with parent linking, authentication, and event handling. The following implementation in interfaces/slack.py demonstrates the complete pattern:

import os
import logging
import asyncio
from slack_sdk.web.async_client import AsyncWebClient
from slack_sdk.socket_mode.aiohttp import SocketModeClient
from slack_sdk.socket_mode.request import SocketModeRequest
from slack_sdk.socket_mode.response import SocketModeResponse
from agents.base_agent import BaseAgent

logger = logging.getLogger(__name__)

class SlackAgent:
    def __init__(self, core_agent=None):
        # Parent linkage pattern required by the framework

        if core_agent is not None and not isinstance(core_agent, BaseAgent):
            raise TypeError("core_agent must be a BaseAgent")
        
        if core_agent:
            super().__setattr__("_parent", core_agent)
        else:
            super().__setattr__("_parent", self)
            super().__init__()

        # Platform-specific authentication

        self.token = os.getenv("SLACK_BOT_TOKEN")
        if not self.token:
            raise ValueError("SLACK_BOT_TOKEN not set")
        
        self.web_client = AsyncWebClient(token=self.token)
        self.socket_client = SocketModeClient(
            app_token=self.token, 
            web_client=self.web_client
        )

        # Register event handlers

        self.socket_client.socket_mode_request_listeners.append(self._process_events)

        # Register interface with CoreAgent

        self.register_interface("slack", self)

    async def _process_events(self, client: SocketModeClient, req: SocketModeRequest):
        if req.type != "events_api":
            return
        
        event = req.payload["event"]
        # Handle simple text messages only

        if event.get("type") == "message" and "subtype" not in event:
            channel = event["channel"]
            user_msg = event["text"]
            
            # Forward to CoreAgent logic

            text_resp, img_url, _ = await self.handle_message(
                message=user_msg,
                source_interface="slack",
                chat_id=channel,
            )
            
            await self.send_message(channel, text_resp, img_url)
        
        # Acknowledge receipt to Slack

        response = SocketModeResponse(envelope_id=req.envelope_id)
        await client.send_socket_mode_response(response)

    async def send_message(self, chat_id: str, message: str, image_url: str | None = None):
        """Required interface method called by CoreAgent."""
        await self.web_client.chat_postMessage(channel=chat_id, text=message)
        
        if image_url:
            await self.web_client.files_upload(
                channels=chat_id, 
                file=image_url, 
                title="Generated image"
            )

    def run(self):
        asyncio.run(self.socket_client.connect())

5. Create an Entry Point (Optional)

For standalone testing, create main_slack.py to instantiate the core agent and attach your interface:

import asyncio
from agents.core_agent import CoreAgent
from interfaces.slack import SlackAgent

async def main():
    core = CoreAgent()
    await core.initialize()
    
    # Instantiate registers the interface automatically

    slack = SlackAgent(core_agent=core)
    
    # Keep the process alive

    await asyncio.Event().wait()

if __name__ == "__main__":
    asyncio.run(main())

6. Register with the Core Agent

When running multiple interfaces, instantiate each one with the same CoreAgent reference:

from agents.core_agent import CoreAgent
from interfaces.slack import SlackAgent
from interfaces.telegram import TelegramAgent

async def main():
    core = CoreAgent()
    await core.initialize()
    
    SlackAgent(core_agent=core)    # Registers as "slack"

    TelegramAgent(core_agent=core) # Registers as "telegram"

    
    await asyncio.Event().wait()

The CoreAgent.send_to_interface() method (located at lines 185-215 in agents/core_agent.py) looks up the target interface by the name provided during registration and invokes its send_message method.

Adapting the Pattern for WhatsApp or Other Platforms

To add a new platform interface for WhatsApp instead of Slack, follow the identical architectural pattern while substituting the client library:

  1. Install the appropriate SDK (e.g., twilio for WhatsApp Business API or yowsup for unofficial protocols) using uv add.
  2. Define environment variables such as WHATSAPP_API_KEY and WHATSAPP_PHONE_NUMBER_ID.
  3. Implement the event loop using the library's webhook or polling mechanism, translating incoming messages into self.handle_message() calls.
  4. Implement send_message using the library's message delivery API.

The framework imposes no constraints on how you receive events—webhooks, WebSockets, or polling—provided you forward the text content to handle_message() and expose the standardized send_message coroutine.

Key Source Files

Understanding these files in the heurist-network/heurist-agent-framework repository helps when debugging or extending your interface:

  • agents/base_agent.py – Defines the abstract interface contract including handle_message, register_interface, and send_to_interface.
  • agents/core_agent.py – Implements the reasoning pipeline, tool orchestration, and knowledge-base lookup. Lines 185-215 contain the send_to_interface dispatch logic.
  • interfaces/telegram.py – Reference implementation showing proper parent linking and message handling.
  • interfaces/discord.py – Alternative example demonstrating WebSocket-based event handling.
  • pyproject.toml – Dependency management file where you add platform-specific SDKs.

Summary

  • Platform interfaces are thin wrappers that translate between external messaging services and the Heurist Agent Framework's core logic.
  • Composition over inheritance: Interfaces store a _parent reference to CoreAgent and delegate attributes via __getattr__, avoiding code duplication.
  • Required methods: Implement send_message(self, chat_id, message, image_url) and forward inbound events to handle_message(source_interface, chat_id, message).
  • Registration: Call self.register_interface("name", self) during initialization to enable the core agent to route replies.
  • Environment configuration: Follow the existing pattern of reading tokens from environment variables defined in .env.example.

Frequently Asked Questions

Do platform interfaces inherit from BaseAgent?

No. Platform interfaces do not inherit from BaseAgent or any other base class. Instead, they use composition by storing a reference to the core agent in _parent and delegating attribute access via __getattr__ and __setattr__. This allows the interface to reuse all core logic—including prompt handling and tool execution—without duplicating code or creating circular dependencies.

How does CoreAgent know which interface to use when sending replies?

When your interface calls self.register_interface("slack", self) during initialization, it adds itself to a registry inside CoreAgent. Later, when the core agent generates a response, it calls send_to_interface() (implemented in agents/core_agent.py lines 185-215), which looks up the interface by the name provided during registration and invokes its send_message method with the appropriate chat_id.

Can I run multiple platform interfaces simultaneously?

Yes. Instantiate multiple interface classes with the same CoreAgent reference in your main entry point. Each interface runs its own event loop or webhook listener. For example, you can run SlackAgent(core_agent=core) and TelegramAgent(core_agent=core) in the same process, and CoreAgent will route responses to the correct platform based on the source_interface parameter passed to handle_message.

Which library should I use for WhatsApp integration?

For WhatsApp Business API, use the official Twilio Python SDK (twilio) or Meta's Business SDK. For unofficial WhatsApp Web protocols, libraries like yowsup or whatsapp-web.js (via Python bridge) are community options. Regardless of the library, you must implement the same three contractual obligations: parent linking, send_message, and forwarding events to handle_message.

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 →