High-density, schema-driven Discord components dialog engine with MCP and ACP backends.


1. πŸš€ Quick Start & Commands

Commands are registered in the Red-DiscordBot Discord slash command registry.

CommandActionKey Options
/dialog-runLaunches a new dialog session--spec <name> (required), --channel <id> (optional)
/dialog-listLists all active dialog sessionsNone
/dialog-cancelTerminates an active session--session <id> (required)
/dialog-reloadHot-reloads all spec YAML definitionsNone

2. πŸ”§ Core Directory Layout

UniDialog is structured as a standard Red-DiscordBot cog, organizing parser, state, rendering, and adapter systems:

unidialog/
β”œβ”€β”€ __init__.py           # Cog entry point registering the cog instance
β”œβ”€β”€ info.json             # Red Bot cog metadata (author, description, requirements)
β”œβ”€β”€ unidialog.py          # Main cog class, handles Discord commands and event routing
β”œβ”€β”€ core/
β”‚   β”œβ”€β”€ parser.py         # YAML loader and JSON Schema validation for dialog specs
β”‚   β”œβ”€β”€ state.py          # Red Config integration and session state store
β”‚   β”œβ”€β”€ engine.py         # Interaction loop router & component handler
β”‚   └── renderer.py       # Discord UI component layout solver
β”œβ”€β”€ adapters/
β”‚   β”œβ”€β”€ base.py           # Abstract base class for backend adapters
β”‚   β”œβ”€β”€ local.py          # Registers & dispatches local in-process Python actions
β”‚   β”œβ”€β”€ mcp.py            # SSE client handling JSON-RPC calls to MCP servers
β”‚   └── acp.py            # Subprocess orchestrator for ACP over stdio pipelines
└── specs/                # Local cache of default dialog specification YAMLs
    └── server_deploy.yml # Sample deployment workflow specification

3. 🐍 Cog API & Component Listeners

UniDialog is implemented as a Python class subclassing Red-DiscordBot’s commands.Cog. It exposes registration interfaces for local extensions and listens directly to Discord interaction events:

from typing import Callable, Dict, Any, Optional
import discord
from redbot.core import commands, Config
 
class UniDialog(commands.Cog):
    """
    Red-DiscordBot cog driving schema-driven components and adapters.
    """
    def __init__(self, bot: commands.Bot):
        self.bot = bot
        self.config = Config.get_conf(self, identifier=89101112)
        self.local_actions: Dict[str, Callable[[discord.Interaction, Any], Any]] = {}
 
    @commands.Cog.listener()
    async def on_interaction(self, interaction: discord.Interaction) -> None:
        """
        Global event listener intercepting component clicks and select menu changes.
        Routes interactions with the 'unidialog:' prefix to the engine.
        """
        pass
 
    def register_local_action(self, name: str, callback: Callable[[discord.Interaction, Any], Any]) -> None:
        """
        Registers a local Python callback to handle in-process actions
        defined in YAML specs using the 'local' adapter.
        """
        self.local_actions[name] = callback

4. πŸ›οΈ Core Architecture (The 5 Layers)

UniDialog executes interactive UI experiences on Discord by organizing operations into a strict 5-layer pipeline.

               β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
               β”‚                   Spec Layer                     β”‚
               β”‚        (YAML Parsing, Schema Validation)         β”‚
               β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                        β”‚
                                        β–Ό
               β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
               β”‚                  State Layer                     β”‚
               β”‚     (Red-DiscordBot Config, User Sessions)       β”‚
               β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                        β”‚
                                        β–Ό
               β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
               β”‚                Renderer Layer                    β”‚
               β”‚        (Layout Constraints, Paging/Grid)         β”‚
               β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                        β”‚
                                        β–Ό
               β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
               β”‚                 Adapter Layer                    β”‚
               β”‚       (Local Python, MCP SSE, ACP Stdio)         β”‚
               β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                        β”‚
                                        β–Ό
               β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
               β”‚                  Loop Layer                      β”‚
               β”‚     (Event Handlers, Deferrals, Redraws)         β”‚
               β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
LayerPrimary RoleCore Logic / Operations
Spec LayerDeclarative YAML ParsingLoads, validates, and builds in-memory representation of Dialog, Screen, and Block definitions. Enforces schemas.
State LayerSession PersistencePersists active navigation stacks, user bindings, input buffers, and session histories to Red Config.
Renderer LayerLayout Constraint SolverTranslates Screen specs into Discord UI components. Manages button grids, dynamic selects, and modals.
Adapter LayerRouting & ExecutionDispatches action handlers and data fetching requests to Local Python functions, MCP (SSE), or ACP (Stdio).
Loop LayerEvent Loop & RedrawIntercepts component interactions, enforces 3s ACKs, schedules deferred updates, and performs message edits.

πŸ”„ Interaction Lifecycle

When a user triggers a component interaction (e.g., button click or select dropdown):

  1. Event Dispatch: Discord sends an interaction payload to the bot.
  2. Loop Interception: The Loop Layer intercepts the event and schedules an immediate deferral (interaction.response.defer()) within the 3-second limit.
  3. Custom ID Parsing: The Loop Layer parses the 100-character custom ID format (unidialog:{guild_id}:{channel_id}:{user_id}:{block_id}:{action_hash}) and performs Command Hijacking Prevention (verifying the interacting user matches the session owner).
  4. State Lookup: The State Layer fetches the active navigation stack and state document from the Red Config database using the parsed session keys.
  5. Spec Mapping: The Spec Layer matches the interaction block ID with the loaded Screen and Block specs.
  6. Action Dispatch: The Adapter Layer routes the action to the defined adapter (Local, MCP, or ACP) and executes it with context/arguments parsed from the event.
  7. Constraint Solving: If state updates occur, the Renderer Layer resolves UI constraints (e.g., button grids, pagination offsets) and builds the new Discord UI component payload.
  8. Redraw: The Loop Layer updates the Discord message with the new content and components, completing the cycle.

5. πŸ”Œ Adapter Mappings

UniDialog bridges Discord interactions with different backend architectures using three distinct adapters: Local Python, Model Context Protocol (MCP), and Agent Context Protocol (ACP).

CapabilityLocal BackendMCP BackendACP Backend
Source PopulationDirect Python calls to registered functionsHTTP/SSE JSON-RPC queries to retrieve dynamic dropdown optionsJSON-RPC calls over Stdio to query running agent state
Action HandlingExecutes local synchronous/asynchronous Python methodsFormulates JSON-RPC method invocations forwarded via SSEStandardized stdio messages processed by spawning agents
Output StreamsReturn-based updates or local file/database modificationsChunked SSE updates from remote serverStdio stream capturing with a 1.2s coalesce buffer
PermissionsRed-DiscordBot role checks & Guild configurationRemote authorization, token-based checks, confirm dialogsSubprocess isolation and prompt-based capability approval
Transport LayersIn-process Python modulesSSE (Server-Sent Events) and HTTP/JSON-RPCSubprocess stdio pipelines (stdin/stdout/stderr)

6. πŸ“± UI/UX & Responsive Rendering Rules

To deliver smooth interactive interfaces inside the constraints of Discord’s UI components, UniDialog enforces strict rendering rules:

1. Constraint Solving

  • Paging Selects: Dropdown menus containing more than 25 options are automatically split into paginated dropdown components with β€œNext Page” and β€œPrevious Page” buttons.
  • Modal Wizards: Forms requiring more than 5 distinct fields are automatically converted into multi-step modal wizards.
  • Button Grids: Buttons are automatically arranged into rows of up to 5 buttons, preventing layout overflow (max 5 rows per message).

2. Throttling & Coalescing

  • When capturing output streams from an ACP subprocess or MCP tool (e.g. streaming logs or thoughts), UniDialog buffers updates using a 1.2s coalesce buffer.
  • Edits to the Discord message are rate-limited to a maximum of 5 edits per 5 seconds to prevent hitting Discord rate limits, while maintaining real-time progress feel.

3. 3-Second ACK Rule

  • For all operations, UniDialog intercepts the Discord interaction and calls interaction.response.defer() within the 3-second API deadline.
  • While the remote tool or agent subprocess is executing, a temporary loading state is displayed in the Discord UI.

4. Persistence on Reboot

  • Every custom component ID is serialized in the format: unidialog:{guild_id}:{channel_id}:{user_id}:{block_id}:{action_hash}
  • This format allows the UniDialog engine to route click interactions back to the correct state store and spec definition, even after bot reboots.

7. βš™οΈ Persistent State Schema (Red Config)

Session states are persisted in the Red-DiscordBot Config database. The nested document structure for a single session is defined as:

{
  "session_id": "123e4567-e89b-12d3-a456-426614174000",
  "spec_id": "server_deploy_dialog",
  "user_id": "123456789012345678",
  "guild_id": "987654321098765432",
  "channel_id": "555555555555555555",
  "message_id": "888888888888888888",
  "nav_stack": ["main_menu", "confirm_screen"],
  "state": {
    "target_env": "production",
    "last_action": "execute_deploy"
  },
  "created_at": 1781258400,
  "updated_at": 1781258450
}

8. πŸ“¦ Data Model & YAML Schema Spec

Dialog engines are defined declaratively in YAML using the following structure:

# Schema definition for unidialog.yml
dialog:
  id: "server_deploy_dialog"
  title: "Server Deployment Console"
  initial_screen: "main_menu"
 
screens:
  main_menu:
    title: "Deployment Overview"
    description: "Select an environment to proceed with deployment."
    blocks:
      - id: "env_select"
        type: "select"
        placeholder: "Choose Target Environment"
        options_provider:
          adapter: "mcp"
          method: "list_environments"
        on_change:
          action: "set_state"
          key: "target_env"
      - id: "deploy_btn"
        type: "button"
        label: "Deploy Now"
        style: "success"
        on_click:
          action: "navigate"
          target: "confirm_screen"
 
  confirm_screen:
    title: "Confirm Deployment"
    description: "Are you sure you want to deploy to {{state.target_env}}?"
    blocks:
      - id: "confirm_btn"
        type: "button"
        label: "Confirm & Launch"
        style: "danger"
        on_click:
          adapter: "acp"
          action: "execute_deploy"
          args:
            env: "{{state.target_env}}"
      - id: "cancel_btn"
        type: "button"
        label: "Cancel"
        style: "secondary"
        on_click:
          action: "navigate"
          target: "main_menu"

9. πŸ”Œ Routes & Integration Protocols

1. MCP (Model Context Protocol) JSON-RPC over SSE

For MCP integrations, UniDialog issues requests to remote MCP servers using standard JSON-RPC 2.0.

Request Structure (Data Fetch / Options):

{
  "jsonrpc": "2.0",
  "method": "tools/call",
  "params": {
    "name": "list_environments",
    "arguments": {}
  },
  "id": 1
}

Response Structure (Success):

{
  "jsonrpc": "2.0",
  "result": {
    "content": [
      {
        "type": "text",
        "text": "[\"production\", \"staging\", \"development\"]"
      }
    ]
  },
  "id": 1
}

Response Structure (Error):

{
  "jsonrpc": "2.0",
  "error": {
    "code": -32603,
    "message": "Internal error: Environment service unreachable",
    "data": "Target service timed out after 5000ms"
  },
  "id": 1
}

2. ACP (Agent Context Protocol) over Stdio

When interacting with local agents, UniDialog spawns the agent process and writes JSON commands to stdin, reading responses from stdout.

Command Input:

{
  "command": "run_agent",
  "agent_id": "deploy_agent",
  "parameters": {
    "env": "production"
  }
}

Stream Output:

{
  "status": "running",
  "log": "Compiling assets...",
  "progress": 0.35
}

10. 🚨 Gotchas, Invariants & Safety

  • Rate Limit Exhaustion: Discord rate limits allow max 5 message edits per 5 seconds. The 1.2s coalesce buffer must be strictly enforced.
  • State Size Limit: Discord custom ID fields have a hard limit of 100 characters. The custom ID format unidialog:{guild_id}:{channel_id}:{user_id}:{block_id}:{action_hash} must fit within this constraint (hash the action if arguments are too large).
  • Zombie Subprocesses: ACP processes spawned over stdio must be aggressively tracked and reaped on dialog cancellation, session timeout, or cog reload.
  • Interaction Expiry: Discord interactions expire in 15 minutes. Long-running tasks must shift from edit interactions to direct message edits via webhook or API channel send.
  • Command Hijacking: Button clicks must validate user_id inside the custom ID against the interaction user. Other users clicking must receive an ephemeral β€œNot your session” error.
  • Stream Terminations: If an ACP subprocess stdout stream terminates abruptly, the coalesce buffer must flush immediately rather than waiting for the remaining duration of the 1.2s timeout window.
  • Select Options Failures: If a dropdown options provider fails (e.g. MCP timeout or bad JSON-RPC response), the engine must not crash or leave a blank UI; it must render a warning option (β€œFailed to load options”) with an error icon and keep the user on the screen.
  • Modal Form Timeout: Since Discord modals expire after approximately 20 minutes if unsubmitted, the engine must handle modal submit errors gracefully, clean up the associated Red Config session state, and release any session locks.
  • RPC Parsing Failures: When receiving malformed JSON or invalid JSON-RPC 2.0 structures from an external MCP server, the MCP adapter must catch exceptions, log the error, and display a standard error UI screen to the user.

11. πŸ” Verification & Diagnostics Requirements

Before releasing any dialog definition or engine update, the following checks must be verified:

  • Custom ID Size Check: Every generated button or dropdown custom ID must be verified to be under 100 characters.
  • Zombie Sweeper: Verify that subprocess managers kill all spawned ACP subprocesses upon receiving a cancellation signal or on session timeout.
  • State Compaction: Validate that active user session states stored in Red Config do not exceed 10KB to maintain database performance.
  • 3-Second ACK Coverage: Confirm that every path leading to external systems (MCP or ACP) defers the interaction response instantly.
  • Rate Limit Coalesce Test: Simulate rapid stream output (e.g. 50 chunks per second) and confirm that the coalesce buffer groups edits into max 5 updates per 5 seconds.
  • Abrupt Stream Termination: Verify that the coalesce buffer flushes instantly upon EOF or process termination.
  • Options Fallback: Inject an MCP options provider failure and verify that the UI falls back to an error option rather than freezing or crashing.
  • Malformed JSON-RPC Handler: Test invalid JSON-RPC server responses and verify they render the standard error screen and do not crash the event loop.

12. πŸš€ Rollout Phases

flowchart LR
    subgraph Core["Core Development"]
        P1["Phase 1: Engine Core & YAML Parser"]
        P2["Phase 2: State Store & Navigation"]
    end
    subgraph Integrations["Integrations"]
        P3["Phase 3: MCP Tool Integration (SSE)"]
        P4["Phase 4: ACP Runner & Safety Gates"]
    end
    subgraph Release["Quality & Release"]
        P5["Phase 5: Release QA & Diagnostics"]
    end
    P1 --> P2 --> P3 --> P4 --> P5

Phase 1: Engine Core & YAML Spec Parser

  • Implement YAML parser and validator for Dialog, Screen, and Block specs.
  • Build the layout constraint solver (paging, wrapping rows).
  • Integrate the Local Python adapter backend.

Phase 2: Persistent State Store & Navigation

  • Setup Red Config schemas for persisting active sessions.
  • Implement the custom ID serialization for click routing.
  • Add back-button/breadcrumb tracking capabilities.

Phase 3: MCP Tool Integration

  • Build Server-Sent Events (SSE) client for remote MCP tool invocation.
  • Map MCP schemas directly to dynamic components (e.g. generating modals from JSON schemas).

Phase 4: ACP Agent Runner & Safety Gates

  • Build subprocess manager for executing ACP agents over stdio.
  • Implement the 1.2s coalesce rate-limiting buffer for output streams.
  • Add confirmation safety gates for dangerous actions.

Phase 5: Release QA & Diagnostics

  • Perform rate-limit stress tests (simulating concurrent button interactions).
  • Verify persistent state survival across simulated bot reboots.
  • Write suite of diagnostic commands.