Skip to content

Mini-App Handler Routing System

Status: Production Ready Version: 1.0 Last Updated: December 2025


Overview

The Handler Routing System provides the conversational interface layer for mini-apps. It processes natural language messages, manages session state, and generates both chat responses and web UI configurations.

Key Capabilities: - Chat message processing and response generation - Session-based state management - Web UI configuration generation (config-driven) - View-specific layouts for different app states - URL generation for web access


Architecture

The handler system works alongside the event-sourcing layer:

┌─────────────────────────────────────────────────────────────┐
│                     User Message                             │
│              "Add pizza $25 to the bill"                     │
└──────────────────┬──────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│              MiniApp Registry                                │
│   • Handler lookup by app_id                                 │
│   • Handler instantiation with config                        │
└──────────────────┬──────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│              Handler (e.g., BillSplitHandler)                │
│   • Parse message commands                                   │
│   • Update session state                                     │
│   • Return MiniAppResponse                                   │
└──────────────────┬──────────────────────────────────────────┘
                   ├─────────────────┐
                   ▼                 ▼
┌─────────────────────────┐  ┌─────────────────────────────┐
│    Chat Response         │  │    Web UI Config             │
│  "Added pizza: $25.00"   │  │  get_ui_config(view)        │
│  + web_ui_url            │  │  → JSON for React frontend   │
└─────────────────────────┘  └─────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│              Session Manager                                 │
│   • Persist session state to database                       │
│   • Track session lifecycle (created → active → completed)  │
└─────────────────────────────────────────────────────────────┘

Core Components

1. BaseMiniAppHandler

Location: app/miniapps/routing/base_handler.py

Abstract base class for all mini-app handlers.

class BaseMiniAppHandler(ABC):
    app_id: str           # e.g., "bill_split"
    app_name: str         # e.g., "Bill Split"
    app_description: str  # Human-readable description
    url_path: str         # URL path segment: /app/{url_path}/{session_id}

    @abstractmethod
    async def handle(self, context: MiniAppContext) -> MiniAppResponse:
        """Process a message and return response."""
        pass

    def get_ui_config(
        self,
        context: MiniAppContext,
        view: str = "overview",
        item_id: Optional[str] = None,
    ) -> Dict[str, Any]:
        """Generate UI configuration for web frontend."""
        pass

    def get_web_ui_url(
        self,
        session_id: str,
        view: Optional[str] = None,
        item_id: Optional[str] = None,
    ) -> str:
        """Generate web UI URL for this session."""
        pass

    def get_available_views(self) -> List[str]:
        """Get list of available views for this app."""
        return ["overview"]

2. MiniAppContext

Location: app/miniapps/routing/base_handler.py

Context passed to handlers with all request information.

@dataclass
class MiniAppContext:
    session_id: str              # Unique session identifier
    user_id: str                 # User identifier (phone or UUID)
    sender: str                  # Message sender
    message: str                 # User's message text
    session_state: str           # Current state: "initial", "active", "completed"
    session_data: Dict[str, Any] # Persistent session state
    is_group: bool               # Group chat flag
    participants: List[str]      # All participants in session
    metadata: Dict[str, Any]     # Additional context

3. MiniAppResponse

Location: app/miniapps/routing/base_handler.py

Response returned by handlers.

@dataclass
class MiniAppResponse:
    message: str                                    # Chat response text
    state_data_updates: Optional[Dict[str, Any]]    # State updates to persist
    new_state: Optional[str]                        # State transition
    end_session: bool = False                       # End session flag
    end_reason: Optional[str] = None                # "completed", "cancelled", "timeout"
    web_ui_url: Optional[str] = None                # Web UI URL (shown on first action)
    web_ui_view: Optional[str] = None               # Specific view to link to

4. SessionManager

Location: app/miniapps/routing/session_manager.py

Manages session persistence and lifecycle.

class SessionManager:
    async def create_session(
        self,
        app_id: str,
        user_id: str,
        initial_state: str = "initial",
        initial_data: Optional[Dict] = None,
    ) -> MiniAppSession

    async def get_session(self, session_id: str) -> Optional[MiniAppSession]

    async def update_session(
        self,
        session_id: str,
        state: Optional[str] = None,
        data_updates: Optional[Dict] = None,
    ) -> MiniAppSession

    async def end_session(
        self,
        session_id: str,
        reason: str = "completed",
    ) -> MiniAppSession

Available Handlers

Trip Planner (trip_planner)

Location: app/miniapps/handlers/trip_planner.py URL Path: /app/trip/{session_id}

Features: - Destination and dates management - Venue collection with attribution - Multi-venue parsing from pasted content - Category and status tracking - Rich UI with map view support

Views: - overview - Trip summary with venues - map - Map view of all venues - venue/{venue_id} - Single venue detail - share - Shareable trip summary

Bill Split (bill_split)

Location: app/miniapps/handlers/bill_split.py URL Path: /app/split/{session_id}

Features: - Item tracking with amounts - Participant management - Payment recording - Split calculations

Views: - overview - Items and people - settlement - Who pays who

Todo List (todo_list)

Location: app/miniapps/handlers/todo_list.py URL Path: /app/todo/{session_id}

Features: - Task management - Priority levels (!, ?, none) - Task assignment - Completion tracking

Views: - overview - Task list with filters

Polls (poll)

Location: app/miniapps/handlers/poll.py URL Path: /app/poll/{session_id}

Features: - Question and options - Vote collection - Result display - Anonymous/public voting

Views: - overview - Voting interface - results - Results display


Creating a New Handler

Step 1: Create Handler File

# app/miniapps/handlers/my_app.py

from typing import Dict, List, Any, Optional
from app.miniapps.routing.base_handler import (
    BaseMiniAppHandler,
    MiniAppContext,
    MiniAppResponse,
)

class MyAppHandler(BaseMiniAppHandler):
    app_id = "my_app"
    app_name = "My App"
    app_description = "Description of what this app does"
    url_path = "myapp"  # URL: /app/myapp/{session_id}

    async def handle(self, context: MiniAppContext) -> MiniAppResponse:
        message = context.message.lower().strip()
        state_data = context.session_data or {}

        # Parse commands
        if message.startswith("add "):
            item = context.message[4:].strip()
            return self._add_item(item, state_data, context.session_id)

        if message == "show":
            return self._show_items(state_data)

        if message == "done":
            return MiniAppResponse(
                message="Session complete!",
                end_session=True,
                end_reason="completed",
            )

        return MiniAppResponse(
            message="Try: add [item], show, or done"
        )

    def _add_item(
        self,
        item: str,
        state_data: Dict,
        session_id: str,
    ) -> MiniAppResponse:
        items = state_data.get("items", [])
        is_first = len(items) == 0

        items.append({"name": item})

        if is_first:
            web_url = self.get_web_ui_url(session_id)
            return MiniAppResponse(
                message=f"Added: {item}\n\nView online: {web_url}",
                state_data_updates={"items": items},
                new_state="active",
                web_ui_url=web_url,
            )

        return MiniAppResponse(
            message=f"Added: {item}",
            state_data_updates={"items": items},
        )

    def get_available_views(self) -> List[str]:
        return ["overview"]

    def get_ui_config(
        self,
        context: MiniAppContext,
        view: str = "overview",
        item_id: Optional[str] = None,
    ) -> Dict[str, Any]:
        state_data = context.session_data or {}
        items = state_data.get("items", [])

        return {
            "app_id": self.app_id,
            "session_id": context.session_id,
            "version": "1.0",
            "title": "My App",
            "header": {
                "title": "My App",
                "subtitle": f"{len(items)} items",
            },
            "components": [
                {
                    "type": "card_list",
                    "id": "items",
                    "props": {
                        "cards": [
                            {"id": str(i), "type": "item", "data": item}
                            for i, item in enumerate(items)
                        ],
                        "empty_message": "No items yet!",
                    },
                },
            ],
        }

Step 2: Register Handler

# app/miniapps/handlers/__init__.py

from app.miniapps.handlers.my_app import MyAppHandler

__all__ = [
    # ... existing handlers
    "MyAppHandler",
]
# app/miniapps/routing/registry.py

def _register_default_handlers(registry: MiniAppRegistry):
    from app.miniapps.handlers import (
        # ... existing imports
        MyAppHandler,
    )

    # ... existing registrations
    registry.register_handler("my_app", MyAppHandler)

Web UI Configuration

The get_ui_config() method returns a JSON configuration that the React frontend renders. This is a config-driven UI approach.

UI Config Structure

{
    "app_id": str,           # Mini-app identifier
    "session_id": str,       # Session identifier
    "version": str,          # Config version
    "title": str,            # Page title
    "theme": str,            # Theme name (optional)

    "header": {
        "title": str,
        "subtitle": str,
        "stats": [{"label": str, "value": str}],
        "progress": {"current": int, "total": int, "label": str},
    },

    "navigation": {
        "type": "tabs",
        "items": [
            {"id": str, "label": str, "icon": str, "view": str}
        ],
        "current": str,
    },

    "components": [
        {
            "type": str,           # Component type
            "id": str,             # Unique ID
            "props": Dict,         # Component-specific props
        }
    ],
}

Available Components

Type Description Key Props
quick_add Text input with action placeholder, action
section_header Section title title, subtitle, action
card_list List of cards cards, empty_message
split_summary Bill split balances total, per_person, balances
action_panel Action buttons actions, sticky
info_banner Info/success/warning type, title, message
settlement_list Payment list settlements, allow_mark_paid
filter_bar Filter controls sections, show_all_option
poll Voting component question, options, user_vote
stats_grid Stats display stats, columns

URL Generation

Web UI URLs

# Base URL pattern
/app/{url_path}/{session_id}[/{view}][/{item_id}]

# Examples
/app/trip/abc123                    # Trip overview
/app/trip/abc123/map                # Trip map view
/app/trip/abc123/venue/v1           # Venue detail
/app/split/xyz789/settlement        # Bill split settlement

Generating URLs

# In handler
web_url = self.get_web_ui_url(session_id)
# Returns: https://app.archety.ai/app/trip/abc123

web_url = self.get_web_ui_url(session_id, view="map")
# Returns: https://app.archety.ai/app/trip/abc123/map

web_url = self.get_web_ui_url(session_id, view="venue", item_id="v1")
# Returns: https://app.archety.ai/app/trip/abc123/venue/v1

Integration with Event Sourcing

The handler routing system and event-sourcing system work together:

  1. Handler Layer - Processes messages, manages sessions, generates UI
  2. Event Sourcing Layer - Provides persistent state, audit trail, multiplayer sync
Handler (chat interface)
    ├── SessionManager (quick reads/writes)
    └── EventStore (optional, for audit trail)
            └── StateCoordinator (state reconstruction)

For simple single-user apps, handlers can use SessionManager directly. For multiplayer apps with audit requirements, handlers should also emit events to EventStore.


API Routes

Get UI Configuration

GET /miniapp/ui/{session_id}?view={view}&item_id={item_id}

Response:

{
    "app_id": "trip_planner",
    "session_id": "abc123",
    "version": "1.0",
    "title": "Trip Planner",
    "header": {...},
    "components": [...]
}

Process Message

POST /miniapp/message
{
    "session_id": "abc123",
    "message": "add pizza $25",
    "user_id": "+15551234567"
}

Response:

{
    "message": "Added pizza: $25.00",
    "session_state": "active",
    "web_ui_url": "https://app.archety.ai/app/split/abc123"
}


Best Practices

Handler Design

  1. Parse commands case-insensitively - message.lower().strip()
  2. Include web URL only on first significant action - Don't spam URLs
  3. Use state_data_updates - Partial updates, not full state replacement
  4. Provide helpful fallback messages - Guide users on valid commands
  5. Support both explicit commands and natural input - "add pizza" and "pizza $25"

UI Config Design

  1. Keep configs declarative - No logic in configs
  2. Use semantic component types - Let frontend handle rendering
  3. Include empty states - What to show when no data
  4. Support progressive disclosure - Collapsed sections, pagination

State Management

  1. Keep state flat - Avoid deep nesting
  2. Use IDs for references - Not nested objects
  3. Include timestamps - created_at, updated_at
  4. Version state schema - For migration compatibility

Debugging

Enable Logging

import logging
logging.getLogger("app.miniapps.handlers").setLevel(logging.DEBUG)

Common Issues

Handler not found: - Check handler is registered in registry.py - Verify app_id matches

State not persisting: - Ensure state_data_updates is returned - Check SessionManager connection

UI not rendering: - Validate JSON schema - Check component types exist in frontend



Last Updated: December 2025 Version: 1.0