Files
loyal_companion/docs/multi-platform-expansion.md
latte dde2649876
All checks were successful
Enterprise AI Code Review / ai-review (pull_request) Successful in 35s
phase 1
2026-01-31 18:44:29 +01:00

14 KiB

Multi-Platform Expansion

Adding Web & CLI Interfaces

This document extends the Loyal Companion architecture beyond Discord. The goal is to support Web and CLI interaction channels while preserving:

  • one shared Living AI core
  • consistent personality & memory
  • attachment-safe A+C hybrid behavior
  • clear separation between platform and cognition

1. Core Principle

Platforms are adapters, not identities.

Discord, Web, and CLI are merely different rooms through which the same companion is accessed.

The companion:

  • remains one continuous entity
  • may adjust tone by platform
  • never fragments into separate personalities

2. New Architectural Layer: Conversation Gateway

Purpose

Introduce a single entry point for all conversations, regardless of platform.

[ Discord Adapter ] ─┐
[ Web Adapter ] ─────┼──▶ ConversationGateway ─▶ Living AI Core
[ CLI Adapter ] ─────┘

Responsibilities

The Conversation Gateway:

  • normalizes incoming messages
  • assigns platform metadata
  • invokes the existing AI + Living AI pipeline
  • returns responses in a platform-agnostic format

Required Data Structure

@dataclass
class ConversationRequest:
    user_id: str                    # Platform-specific user ID
    platform: Platform              # Enum: DISCORD | WEB | CLI
    session_id: str                 # Conversation/channel identifier
    message: str                    # User's message content
    context: ConversationContext    # Additional metadata

@dataclass
class ConversationContext:
    is_public: bool                 # Public channel vs private
    intimacy_level: IntimacyLevel   # LOW | MEDIUM | HIGH
    platform_metadata: dict         # Platform-specific extras
    guild_id: str | None = None     # Discord guild (if applicable)
    channel_id: str | None = None   # Discord/Web channel

Current Implementation Location

Existing message handling: src/loyal_companion/cogs/ai_chat.py

The current _generate_response_with_db() method contains all the logic that will be extracted into the Conversation Gateway:

  • History loading
  • Living AI context gathering (mood, relationship, style, opinions)
  • System prompt enhancement
  • AI invocation
  • Post-response Living AI updates

Goal: Extract this into a platform-agnostic service layer.


3. Platform Metadata & Intimacy Levels

Intimacy Levels (Important for A+C Safety)

Intimacy level influences:

  • language warmth
  • depth of reflection
  • frequency of proactive behavior
  • memory surfacing
Platform Default Intimacy Notes
Discord (guild) LOW Social, public, shared
Discord (DM) MEDIUM Private but casual
Web HIGH Intentional, reflective
CLI HIGH Quiet, personal, focused

Intimacy Level Behavior Modifiers

LOW (Discord Guild):

  • Less emotional intensity
  • More grounding language
  • Minimal proactive behavior
  • Surface-level memory recall only
  • Shorter responses
  • Public-safe topics only

MEDIUM (Discord DM):

  • Balanced warmth
  • Casual tone
  • Moderate proactive behavior
  • Personal memory recall allowed
  • Normal response length

HIGH (Web/CLI):

  • Deeper reflection permitted
  • Silence tolerance (not rushing to respond)
  • Proactive check-ins allowed
  • Deep memory surfacing
  • Longer, more thoughtful responses
  • Emotional naming encouraged

4. Web Platform

Goal

Provide a private, 1-on-1 chat interface for deeper, quieter conversations than Discord allows.

Architecture

  • Backend: FastAPI (async Python web framework)
  • Transport: HTTP REST + optional WebSocket
  • Auth: Magic link / JWT token / local account
  • No guilds, no other users visible
  • Session persistence via database

Backend Components

New API Module Structure

src/loyal_companion/web/
├── __init__.py
├── app.py                  # FastAPI application factory
├── dependencies.py         # Dependency injection (DB sessions, auth)
├── middleware.py           # CORS, rate limiting, error handling
├── routes/
│   ├── __init__.py
│   ├── chat.py             # POST /chat, WebSocket /ws
│   ├── session.py          # GET/POST /sessions
│   ├── history.py          # GET /sessions/{id}/history
│   └── auth.py             # POST /auth/login, /auth/verify
├── models.py               # Pydantic request/response models
└── adapter.py              # Web → ConversationGateway adapter

Chat Flow

  1. User sends message via web UI
  2. Web adapter creates ConversationRequest
  3. ConversationGateway.process_message() invoked
  4. Living AI generates response
  5. Response returned as JSON

Example API Request

POST /chat

{
  "session_id": "abc123",
  "message": "I'm having a hard evening."
}

Response:

{
  "response": "That sounds heavy. Want to sit with it for a bit?",
  "mood": {
    "label": "calm",
    "valence": 0.2,
    "arousal": -0.3
  },
  "relationship_level": "close_friend"
}

Authentication

Phase 1: Simple token-based auth

  • User registers with email
  • Server sends magic link
  • Token stored in HTTP-only cookie

Phase 2: Optional OAuth integration

UI Considerations (Out of Scope for Core)

The web UI should:

  • Use minimal chat bubbles (user left, bot right)
  • Avoid typing indicators from others (no other users)
  • Optional timestamps
  • No engagement metrics (likes, seen, read receipts)
  • No "X is typing..." unless real-time WebSocket
  • Dark mode default

Recommended stack:

  • Frontend: SvelteKit / React / Vue
  • Styling: TailwindCSS
  • Real-time: WebSocket for live chat

5. CLI Platform

Goal

A local, quiet, terminal-based interface for people who want presence without noise.

Invocation

loyal-companion talk

or (short alias):

lc talk

CLI Behavior

  • Single ongoing session by default
  • Optional named sessions (lc talk --session work)
  • No emojis unless explicitly enabled
  • Text-first, reflective tone
  • Minimal output (no spinners, no progress bars)
  • Supports piping and scripting

Architecture

CLI is a thin client, not the AI itself. It communicates with the web backend via HTTP.

cli/
├── __init__.py
├── main.py             # Typer CLI app entry point
├── client.py           # HTTP client for web backend
├── session.py          # Local session persistence (.lc/sessions.json)
├── config.py           # CLI-specific config (~/.lc/config.toml)
└── formatters.py       # Response formatting for terminal

Session Management

Sessions are stored locally:

~/.lc/
├── config.toml         # API endpoint, auth token, preferences
└── sessions.json       # Session ID → metadata mapping

Session lifecycle:

  1. First lc talk → creates default session, stores ID locally
  2. Subsequent calls → reuses session ID
  3. lc talk --new → starts fresh session
  4. lc talk --session work → named session

Example Interaction

$ lc talk
Bartender is here.

You: I miss someone tonight.

Bartender: That kind of missing doesn't ask to be solved.
           Do you want to talk about what it feels like in your body,
           or just let it be here for a moment?

You: Just let it be.

Bartender: Alright. I'm here.

You: ^D

Session saved.

CLI Commands

Command Purpose
lc talk Start/resume conversation
lc talk --session <name> Named session
lc talk --new Start fresh session
lc history Show recent exchanges
lc sessions List all sessions
lc config Show/edit configuration
lc auth Authenticate with server

6. Shared Identity & Memory

Relationship Model

All platforms share:

  • the same User record (keyed by platform-specific ID)
  • the same UserRelationship
  • the same long-term memory (UserFact)
  • the same mood history

But:

  • contextual behavior varies by intimacy level
  • expression adapts to platform norms
  • intensity is capped per platform

Cross-Platform User Identity

Challenge: A user on Discord and CLI are the same person.

Solution:

  1. Each platform creates a User record with platform-specific ID
  2. Introduce PlatformIdentity linking model
class PlatformIdentity(Base):
    __tablename__ = "platform_identities"
    
    id: Mapped[int] = mapped_column(primary_key=True)
    user_id: Mapped[int] = mapped_column(ForeignKey("users.id"))
    platform: Mapped[Platform] = mapped_column(Enum(Platform))
    platform_user_id: Mapped[str] = mapped_column(String, unique=True)
    
    user: Mapped["User"] = relationship(back_populates="identities")

Later enhancement: Account linking UI for users to connect platforms.

Example Cross-Platform Memory Surfacing

A memory learned via CLI:

"User tends to feel lonelier at night."

May surface on Web (HIGH intimacy):

"You've mentioned nights can feel heavier for you."

But not in Discord guild chat (LOW intimacy).


7. Safety Rules per Platform

Web & CLI (HIGH Intimacy)

Allowed:

  • Deeper reflection
  • Naming emotions ("That sounds like grief")
  • Silence tolerance (not rushing responses)
  • Proactive follow-ups ("You mentioned feeling stuck yesterday—how's that today?")

Still forbidden:

  • Exclusivity claims ("I'm the only one who truly gets you")
  • Dependency reinforcement ("You need me")
  • Discouraging external connection ("They don't understand like I do")
  • Romantic/sexual framing
  • Crisis intervention (always defer to professionals)

Discord DM (MEDIUM Intimacy)

Allowed:

  • Personal memory references
  • Emotional validation
  • Moderate warmth

Constraints:

  • Less proactive behavior than Web/CLI
  • Lighter tone
  • Shorter responses

Discord Guild (LOW Intimacy)

Allowed:

  • Light banter
  • Topic-based conversation
  • Public-safe responses

Additional constraints:

  • No personal memory surfacing
  • No emotional intensity
  • No proactive check-ins
  • Grounding language only
  • Short responses

8. Configuration Additions

New Settings (config.py)

# Platform Toggles
web_enabled: bool = True
cli_enabled: bool = True

# Web Server
web_host: str = "127.0.0.1"
web_port: int = 8080
web_cors_origins: list[str] = ["http://localhost:3000"]
web_auth_secret: str = Field(..., env="WEB_AUTH_SECRET")

# CLI
cli_default_intimacy: IntimacyLevel = IntimacyLevel.HIGH
cli_allow_emoji: bool = False

# Intimacy Scaling
intimacy_enabled: bool = True
intimacy_discord_guild: IntimacyLevel = IntimacyLevel.LOW
intimacy_discord_dm: IntimacyLevel = IntimacyLevel.MEDIUM
intimacy_web: IntimacyLevel = IntimacyLevel.HIGH
intimacy_cli: IntimacyLevel = IntimacyLevel.HIGH

Environment Variables

# Platform Toggles
WEB_ENABLED=true
CLI_ENABLED=true

# Web
WEB_HOST=127.0.0.1
WEB_PORT=8080
WEB_AUTH_SECRET=<random-secret>

# CLI
CLI_DEFAULT_INTIMACY=high
CLI_ALLOW_EMOJI=false

9. Implementation Order

Phase 1: Extract Conversation Gateway

Goal: Create platform-agnostic conversation processor

Files to create:

  • src/loyal_companion/services/conversation_gateway.py
  • src/loyal_companion/models/platform.py (enums, request/response types)

Tasks:

  1. Define Platform enum (DISCORD, WEB, CLI)
  2. Define IntimacyLevel enum (LOW, MEDIUM, HIGH)
  3. Define ConversationRequest and ConversationResponse dataclasses
  4. Extract logic from cogs/ai_chat.py into gateway
  5. Add intimacy-level-based prompt modifiers

Phase 2: Refactor Discord to Use Gateway

Files to modify:

  • src/loyal_companion/cogs/ai_chat.py

Tasks:

  1. Import ConversationGateway
  2. Replace _generate_response_with_db() with gateway call
  3. Build ConversationRequest from Discord message
  4. Format ConversationResponse for Discord output
  5. Test that Discord functionality unchanged

Phase 3: Add Web Platform 🌐

Files to create:

  • src/loyal_companion/web/ (entire module)
  • src/loyal_companion/web/app.py
  • src/loyal_companion/web/routes/chat.py

Tasks:

  1. Set up FastAPI application
  2. Add authentication middleware
  3. Create /chat endpoint
  4. Create WebSocket endpoint (optional)
  5. Add session management
  6. Test with Postman/curl

Phase 4: Add CLI Client 💻

Files to create:

  • cli/ (new top-level directory)
  • cli/main.py
  • cli/client.py

Tasks:

  1. Create Typer CLI app
  2. Add talk command
  3. Add local session persistence
  4. Add authentication flow
  5. Test end-to-end with web backend

Phase 5: Intimacy Scaling 🔒

Files to create:

  • src/loyal_companion/services/intimacy_service.py

Tasks:

  1. Define intimacy level behavior modifiers
  2. Modify system prompt based on intimacy
  3. Filter proactive behavior by intimacy
  4. Add memory surfacing rules
  5. Add safety constraint enforcement

Phase 6: Safety Regression Tests 🛡️

Files to create:

  • tests/test_safety_constraints.py
  • tests/test_intimacy_boundaries.py

Tasks:

  1. Test no exclusivity claims at any intimacy level
  2. Test no dependency reinforcement
  3. Test intimacy boundaries respected
  4. Test proactive behavior filtered by platform
  5. Test memory surfacing respects intimacy

10. Non-Goals

This expansion does NOT aim to:

  • Duplicate Discord features (guilds, threads, reactions)
  • Introduce social feeds or timelines
  • Add notifications or engagement streaks
  • Increase engagement artificially
  • Create a "social network"
  • Add gamification mechanics

The goal is availability, not addiction.


11. Outcome

When complete:

  • Discord is the social bar — casual, public, low-commitment
  • Web is the quiet back room — intentional, private, reflective
  • CLI is the empty table at closing time — minimal, focused, silent presence

Same bartender. Different stools. No one is trapped.


12. Current Implementation Status

Completed

  • None yet

In Progress

  • 🔄 Documentation update
  • 🔄 Phase 1: Conversation Gateway extraction

Planned

  • Phase 2: Discord refactor
  • Phase 3: Web platform
  • Phase 4: CLI client
  • Phase 5: Intimacy scaling
  • Phase 6: Safety tests

Next Steps

See Implementation Guide for detailed Phase 1 instructions.