# 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. ```text [ 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 ```python @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** ```json { "session_id": "abc123", "message": "I'm having a hard evening." } ``` **Response:** ```json { "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 ```bash loyal-companion talk ``` or (short alias): ```bash 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 ```text $ 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 ` | 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 ```python 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) ```python # 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 ```env # Platform Toggles WEB_ENABLED=true CLI_ENABLED=true # Web WEB_HOST=127.0.0.1 WEB_PORT=8080 WEB_AUTH_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 - ✅ Phase 1: Conversation Gateway extraction - ✅ Phase 2: Discord refactor (47% code reduction!) - ✅ Phase 3: Web platform (FastAPI + Web UI complete!) ### In Progress - ⏳ None ### Planned - ⏳ Phase 4: CLI client - ⏳ Phase 5: Platform enhancements (JWT, WebSocket, account linking) - ⏳ Phase 6: Safety regression tests --- ## Next Steps **Phase 1, 2 & 3 Complete!** 🎉🌐 See implementation details: - [Phase 1: Conversation Gateway](implementation/conversation-gateway.md) - [Phase 2: Discord Refactor](implementation/phase-2-complete.md) - [Phase 3: Web Platform](implementation/phase-3-complete.md) **Ready for Phase 4: CLI Client** - See Section 5 for architecture details.