diff --git a/.env.logging.example b/.env.logging.example new file mode 100644 index 0000000..b232a62 --- /dev/null +++ b/.env.logging.example @@ -0,0 +1,132 @@ +# ============================================================================ +# CORTEX LOGGING CONFIGURATION +# ============================================================================ +# This file contains all logging-related environment variables for the +# Cortex reasoning pipeline. Copy this to your .env file and adjust as needed. +# +# Log Detail Levels: +# minimal - Only errors and critical events +# summary - Stage completion + errors (DEFAULT - RECOMMENDED FOR PRODUCTION) +# detailed - Include raw LLM outputs, RAG results, timing breakdowns +# verbose - Everything including intermediate states, full JSON dumps +# +# Quick Start: +# - For debugging weak links: LOG_DETAIL_LEVEL=detailed +# - For finding performance bottlenecks: LOG_DETAIL_LEVEL=detailed + VERBOSE_DEBUG=true +# - For production: LOG_DETAIL_LEVEL=summary +# - For silent mode: LOG_DETAIL_LEVEL=minimal +# ============================================================================ + +# ----------------------------- +# Primary Logging Level +# ----------------------------- +# Controls overall verbosity across all components +LOG_DETAIL_LEVEL=detailed + +# Legacy verbose debug flag (kept for compatibility) +# When true, enables maximum logging including raw data dumps +VERBOSE_DEBUG=false + +# ----------------------------- +# LLM Logging +# ----------------------------- +# Enable raw LLM response logging (only works with detailed/verbose levels) +# Shows full JSON responses from each LLM backend call +# Set to "true" to see exact LLM outputs for debugging weak links +LOG_RAW_LLM_RESPONSES=true + +# ----------------------------- +# Context Logging +# ----------------------------- +# Show full raw intake data (L1-L30 summaries) in logs +# WARNING: Very verbose, use only for deep debugging +LOG_RAW_CONTEXT_DATA=false + +# ----------------------------- +# Loop Detection & Protection +# ----------------------------- +# Enable duplicate message detection to prevent processing loops +ENABLE_DUPLICATE_DETECTION=true + +# Maximum number of messages to keep in session history (prevents unbounded growth) +# Older messages are trimmed automatically +MAX_MESSAGE_HISTORY=100 + +# Session TTL in hours - sessions inactive longer than this are auto-expired +SESSION_TTL_HOURS=24 + +# ----------------------------- +# NeoMem / RAG Logging +# ----------------------------- +# Relevance score threshold for NeoMem results +RELEVANCE_THRESHOLD=0.4 + +# Enable NeoMem long-term memory retrieval +NEOMEM_ENABLED=false + +# ----------------------------- +# Autonomous Features +# ----------------------------- +# Enable autonomous tool invocation (RAG, WEB, WEATHER, CODEBRAIN) +ENABLE_AUTONOMOUS_TOOLS=true + +# Confidence threshold for autonomous tool invocation (0.0 - 1.0) +AUTONOMOUS_TOOL_CONFIDENCE_THRESHOLD=0.6 + +# Enable proactive monitoring and suggestions +ENABLE_PROACTIVE_MONITORING=true + +# Minimum priority for proactive suggestions to be included (0.0 - 1.0) +PROACTIVE_SUGGESTION_MIN_PRIORITY=0.6 + +# ============================================================================ +# EXAMPLE LOGGING OUTPUT AT DIFFERENT LEVELS +# ============================================================================ +# +# LOG_DETAIL_LEVEL=summary (RECOMMENDED): +# ──────────────────────────────────────────────────────────────────────────── +# ✅ [LLM] PRIMARY | 14:23:45.123 | Reply: Based on your question about... +# 📊 Context | Session: abc123 | Messages: 42 | Last: 5.2min | RAG: 3 results +# 🧠 Monologue | question | Tone: curious +# ✨ PIPELINE COMPLETE | Session: abc123 | Total: 1250ms +# 📤 Output: 342 characters +# ──────────────────────────────────────────────────────────────────────────── +# +# LOG_DETAIL_LEVEL=detailed (FOR DEBUGGING): +# ──────────────────────────────────────────────────────────────────────────── +# 🚀 PIPELINE START | Session: abc123 | 14:23:45.123 +# 📝 User: What is the meaning of life? +# ──────────────────────────────────────────────────────────────────────────── +# 🧠 LLM CALL | Backend: PRIMARY | 14:23:45.234 +# ──────────────────────────────────────────────────────────────────────────── +# 📝 Prompt: You are Lyra, a thoughtful AI assistant... +# 💬 Reply: Based on philosophical perspectives, the meaning... +# ╭─ RAW RESPONSE ──────────────────────────────────────────────────────────── +# │ { +# │ "choices": [ +# │ { +# │ "message": { +# │ "content": "Based on philosophical perspectives..." +# │ } +# │ } +# │ ] +# │ } +# ╰─────────────────────────────────────────────────────────────────────────── +# +# ✨ PIPELINE COMPLETE | Session: abc123 | Total: 1250ms +# ⏱️ Stage Timings: +# context : 150ms ( 12.0%) +# identity : 10ms ( 0.8%) +# monologue : 200ms ( 16.0%) +# reasoning : 450ms ( 36.0%) +# refinement : 300ms ( 24.0%) +# persona : 140ms ( 11.2%) +# ──────────────────────────────────────────────────────────────────────────── +# +# LOG_DETAIL_LEVEL=verbose (MAXIMUM DEBUG): +# Same as detailed but includes: +# - Full 50+ line raw JSON dumps +# - Complete intake data structures +# - All intermediate processing states +# - Detailed traceback on errors +# ============================================================================ diff --git a/CHANGELOG.md b/CHANGELOG.md index f5784f7..7da96ec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,271 @@ Format based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and [Se --- +## [0.7.0] - 2025-12-21 + +### Added - Standard Mode & UI Enhancements + +**Standard Mode Implementation** +- Added "Standard Mode" chat option that bypasses complex cortex reasoning pipeline + - Provides simple chatbot functionality for coding and practical tasks + - Maintains full conversation context across messages + - Backend-agnostic - works with SECONDARY (Ollama), OPENAI, or custom backends + - Created `/simple` endpoint in Cortex router [cortex/router.py:389](cortex/router.py#L389) +- Mode selector in UI with toggle between Standard and Cortex modes + - Standard Mode: Direct LLM chat with context retention + - Cortex Mode: Full 7-stage reasoning pipeline (unchanged) + +**Backend Selection System** +- UI settings modal with LLM backend selection for Standard Mode + - Radio button selector: SECONDARY (Ollama/Qwen), OPENAI (GPT-4o-mini), or custom + - Backend preference persisted in localStorage + - Custom backend text input for advanced users +- Backend parameter routing through entire stack: + - UI sends `backend` parameter in request body + - Relay forwards backend selection to Cortex + - Cortex `/simple` endpoint respects user's backend choice +- Environment-based fallback: Uses `STANDARD_MODE_LLM` if no backend specified + +**Session Management Overhaul** +- Complete rewrite of session system to use server-side persistence + - File-based storage in `core/relay/sessions/` directory + - Session files: `{sessionId}.json` for history, `{sessionId}.meta.json` for metadata + - Server is source of truth - sessions sync across browsers and reboots +- Session metadata system for friendly names + - Sessions display custom names instead of random IDs + - Rename functionality in session dropdown + - Last modified timestamps and message counts +- Full CRUD API for sessions in Relay: + - `GET /sessions` - List all sessions with metadata + - `GET /sessions/:id` - Retrieve session history + - `POST /sessions/:id` - Save session history + - `PATCH /sessions/:id/metadata` - Update session name/metadata + - `DELETE /sessions/:id` - Delete session and metadata +- Session management UI in settings modal: + - List of all sessions with message counts and timestamps + - Delete button for each session with confirmation + - Automatic session cleanup when deleting current session + +**UI Improvements** +- Settings modal with hamburger menu (⚙ Settings button) + - Backend selection section for Standard Mode + - Session management section with delete functionality + - Clean modal overlay with cyberpunk theme + - ESC key and click-outside to close +- Light/Dark mode toggle with dark mode as default + - Theme preference persisted in localStorage + - CSS variables for seamless theme switching + - Toggle button shows current mode (🌙 Dark Mode / ☀️ Light Mode) +- Removed redundant model selector dropdown from header +- Fixed modal positioning and z-index layering + - Modal moved outside #chat container for proper rendering + - Fixed z-index: overlay (999), modal content (1001) + - Centered modal with proper backdrop blur + +**Context Retention for Standard Mode** +- Integration with Intake module for conversation history + - Added `get_recent_messages()` function in intake.py + - Standard Mode retrieves last 20 messages from session buffer + - Full context sent to LLM on each request +- Message array format support in LLM router: + - Updated Ollama provider to accept `messages` parameter + - Updated OpenAI provider to accept `messages` parameter + - Automatic conversion from messages to prompt string for non-chat APIs + +### Changed - Architecture & Routing + +**Relay Server Updates** [core/relay/server.js](core/relay/server.js) +- ES module migration for session persistence: + - Imported `fs/promises`, `path`, `fileURLToPath` for file operations + - Created `SESSIONS_DIR` constant for session storage location +- Mode-based routing in both `/chat` and `/v1/chat/completions` endpoints: + - Extracts `mode` parameter from request body (default: "cortex") + - Routes to `CORTEX_SIMPLE` for Standard Mode, `CORTEX_REASON` for Cortex Mode + - Backend parameter only used in Standard Mode +- Session persistence functions: + - `ensureSessionsDir()` - Creates sessions directory if needed + - `loadSession(sessionId)` - Reads session history from file + - `saveSession(sessionId, history, metadata)` - Writes session to file + - `loadSessionMetadata(sessionId)` - Reads session metadata + - `saveSessionMetadata(sessionId, metadata)` - Updates session metadata + - `listSessions()` - Returns all sessions with metadata, sorted by last modified + - `deleteSession(sessionId)` - Removes session and metadata files + +**Cortex Router Updates** [cortex/router.py](cortex/router.py) +- Added `backend` field to `ReasonRequest` Pydantic model (optional) +- Created `/simple` endpoint for Standard Mode: + - Bypasses reflection, reasoning, refinement stages + - Direct LLM call with conversation context + - Uses backend from request or falls back to `STANDARD_MODE_LLM` env variable + - Returns simple response structure without reasoning artifacts +- Backend selection logic in `/simple`: + - Normalizes backend names to uppercase + - Maps UI backend names to system backend names + - Validates backend availability before calling + +**Intake Integration** [cortex/intake/intake.py](cortex/intake/intake.py) +- Added `get_recent_messages(session_id, limit)` function: + - Retrieves last N messages from session buffer + - Returns empty list if session doesn't exist + - Used by `/simple` endpoint for context retrieval + +**LLM Router Enhancements** [cortex/llm/llm_router.py](cortex/llm/llm_router.py) +- Added `messages` parameter support across all providers +- Automatic message-to-prompt conversion for legacy APIs +- Chat completion format for Ollama and OpenAI providers +- Stop sequences for MI50/DeepSeek R1 to prevent runaway generation: + - `"User:"`, `"\nUser:"`, `"Assistant:"`, `"\n\n\n"` + +**Environment Configuration** [.env](.env) +- Added `STANDARD_MODE_LLM=SECONDARY` for default Standard Mode backend +- Added `CORTEX_SIMPLE_URL=http://cortex:7081/simple` for routing + +**UI Architecture** [core/ui/index.html](core/ui/index.html) +- Server-based session loading system: + - `loadSessionsFromServer()` - Fetches sessions from Relay API + - `renderSessions()` - Populates session dropdown from server data + - Session state synchronized with server on every change +- Backend selection persistence: + - Loads saved backend from localStorage on page load + - Includes backend parameter in request body when in Standard Mode + - Settings modal pre-selects current backend choice +- Dark mode by default: + - Checks localStorage for theme preference + - Sets dark theme if no preference found + - Toggle button updates localStorage and applies theme + +**CSS Styling** [core/ui/style.css](core/ui/style.css) +- Light mode CSS variables: + - `--bg-dark: #f5f5f5` (light background) + - `--text-main: #1a1a1a` (dark text) + - `--text-fade: #666` (dimmed text) +- Dark mode CSS variables (default): + - `--bg-dark: #0a0a0a` (dark background) + - `--text-main: #e6e6e6` (light text) + - `--text-fade: #999` (dimmed text) +- Modal positioning fixes: + - `position: fixed` with `top: 50%`, `left: 50%`, `transform: translate(-50%, -50%)` + - Z-index layering: overlay (999), content (1001) + - Backdrop blur effect on modal overlay +- Session list styling: + - Session item cards with hover effects + - Delete button with red hover state + - Message count and timestamp display + +### Fixed - Critical Issues + +**DeepSeek R1 Runaway Generation** +- Root cause: R1 reasoning model generates thinking process and hallucinates conversations +- Solution: + - Changed `STANDARD_MODE_LLM` to SECONDARY (Ollama/Qwen) instead of PRIMARY (MI50/R1) + - Added stop sequences to MI50 provider to prevent continuation + - Documented R1 limitations for Standard Mode usage + +**Context Not Maintained in Standard Mode** +- Root cause: `/simple` endpoint didn't retrieve conversation history from Intake +- Solution: + - Created `get_recent_messages()` function in intake.py + - Standard Mode now pulls last 20 messages from session buffer + - Full context sent to LLM with each request +- User feedback: "it's saying it hasn't received any other messages from me, so it looks like the standard mode llm isn't getting the full chat" + +**OpenAI Backend 400 Errors** +- Root cause: OpenAI provider only accepted prompt strings, not messages arrays +- Solution: Updated OpenAI provider to support messages parameter like Ollama +- Now handles chat completion format correctly + +**Modal Formatting Issues** +- Root cause: Settings modal inside #chat container with overflow constraints +- Symptoms: Modal appearing at bottom, jumbled layout, couldn't close +- Solution: + - Moved modal outside #chat container to be direct child of body + - Changed positioning from absolute to fixed + - Added proper z-index layering (overlay: 999, content: 1001) + - Removed old model selector from header +- User feedback: "the formating for the settings is all off. Its at the bottom and all jumbling together, i cant get it to go away" + +**Session Persistence Broken** +- Root cause: Sessions stored only in localStorage, not synced with server +- Symptoms: Sessions didn't persist across browsers or reboots, couldn't load messages +- Solution: Complete rewrite of session system + - Implemented server-side file persistence in Relay + - Created CRUD API endpoints for session management + - Updated UI to load sessions from server instead of localStorage + - Added metadata system for session names + - Sessions now survive container restarts and sync across browsers +- User feedback: "sessions seem to exist locally only, i cant get them to actually load any messages and there is now way to delete them. If i open the ui in a different browser those arent there." + +### Technical Improvements + +**Backward Compatibility** +- All changes include defaults to maintain existing behavior +- Cortex Mode completely unchanged - still uses full 7-stage pipeline +- Standard Mode is opt-in via UI mode selector +- If no backend specified, falls back to `STANDARD_MODE_LLM` env variable +- Existing requests without mode parameter default to "cortex" + +**Code Quality** +- Consistent async/await patterns throughout stack +- Proper error handling with fallbacks +- Clean separation between Standard and Cortex modes +- Session persistence abstracted into helper functions +- Modular UI code with clear event handlers + +**Performance** +- Standard Mode bypasses 6 of 7 reasoning stages for faster responses +- Session loading optimized with file-based caching +- Backend selection happens once per message, not per LLM call +- Minimal overhead for mode detection and routing + +### Architecture - Dual-Mode Chat System + +**Standard Mode Flow:** +``` +User (UI) → Relay → Cortex /simple → Intake (get_recent_messages) +→ LLM (direct call with context) → Relay → UI +``` + +**Cortex Mode Flow (Unchanged):** +``` +User (UI) → Relay → Cortex /reason → Reflection → Reasoning +→ Refinement → Persona → Relay → UI +``` + +**Session Persistence:** +``` +UI → POST /sessions/:id → Relay → File system (sessions/*.json) +UI → GET /sessions → Relay → List all sessions → UI dropdown +``` + +### Known Limitations + +**Standard Mode:** +- No reflection, reasoning, or refinement stages +- No RAG integration (same as Cortex Mode - currently disabled) +- No NeoMem memory storage (same as Cortex Mode - currently disabled) +- DeepSeek R1 not recommended for Standard Mode (generates reasoning artifacts) + +**Session Management:** +- Sessions stored in container filesystem - need volume mount for true persistence +- No session import/export functionality yet +- No session search or filtering + +### Migration Notes + +**For Users Upgrading:** +1. Existing sessions in localStorage will not automatically migrate to server +2. Create new sessions after upgrade for server-side persistence +3. Theme preference (light/dark) will be preserved from localStorage +4. Backend preference will default to SECONDARY if not previously set + +**For Developers:** +1. Relay now requires `fs/promises` for session persistence +2. Cortex `/simple` endpoint expects `backend` parameter (optional) +3. UI sends `mode` and `backend` parameters in request body +4. Session files stored in `core/relay/sessions/` directory + +--- + ## [0.6.0] - 2025-12-18 ### Added - Autonomy System (Phase 1 & 2) diff --git a/LOGGING_MIGRATION.md b/LOGGING_MIGRATION.md new file mode 100644 index 0000000..8ae5d56 --- /dev/null +++ b/LOGGING_MIGRATION.md @@ -0,0 +1,178 @@ +# Logging System Migration Complete + +## ✅ What Changed + +The old `VERBOSE_DEBUG` logging system has been completely replaced with the new structured `LOG_DETAIL_LEVEL` system. + +### Files Modified + +1. **[.env](.env)** - Removed `VERBOSE_DEBUG`, cleaned up duplicate `LOG_DETAIL_LEVEL` settings +2. **[cortex/.env](cortex/.env)** - Removed `VERBOSE_DEBUG` from cortex config +3. **[cortex/router.py](cortex/router.py)** - Replaced `VERBOSE_DEBUG` checks with `LOG_DETAIL_LEVEL` +4. **[cortex/context.py](cortex/context.py)** - Replaced `VERBOSE_DEBUG` with `LOG_DETAIL_LEVEL`, removed verbose file logging setup + +## 🎯 New Logging Configuration + +### Single Environment Variable + +Set `LOG_DETAIL_LEVEL` in your `.env` file: + +```bash +LOG_DETAIL_LEVEL=detailed +``` + +### Logging Levels + +| Level | Lines/Message | What You See | +|-------|---------------|--------------| +| **minimal** | 1-2 | Only errors and critical events | +| **summary** | 5-7 | Pipeline completion, errors, warnings (production mode) | +| **detailed** | 30-50 | LLM outputs, timing breakdowns, context (debugging mode) | +| **verbose** | 100+ | Everything including raw JSON dumps (deep debugging) | + +## 📊 What You Get at Each Level + +### Summary Mode (Production) +``` +📊 Context | Session: abc123 | Messages: 42 | Last: 5.2min | RAG: 3 results +🧠 Monologue | question | Tone: curious + +==================================================================================================== +✨ PIPELINE COMPLETE | Session: abc123 | Total: 1250ms +==================================================================================================== +📤 Output: 342 characters +==================================================================================================== +``` + +### Detailed Mode (Debugging - RECOMMENDED) +``` +==================================================================================================== +🚀 PIPELINE START | Session: abc123 | 14:23:45.123 +==================================================================================================== +📝 User: What is the meaning of life? +──────────────────────────────────────────────────────────────────────────────────────────────────── + +──────────────────────────────────────────────────────────────────────────────────────────────────── +🧠 LLM CALL | Backend: PRIMARY | 14:23:45.234 +──────────────────────────────────────────────────────────────────────────────────────────────────── +📝 Prompt: You are Lyra, analyzing the user's question... +💬 Reply: Based on the context provided, here's my analysis... +──────────────────────────────────────────────────────────────────────────────────────────────────── + +📊 Context | Session: abc123 | Messages: 42 | Last: 5.2min | RAG: 3 results +──────────────────────────────────────────────────────────────────────────────────────────────────── +[CONTEXT] Session abc123 | User: What is the meaning of life? +──────────────────────────────────────────────────────────────────────────────────────────────────── + Mode: default | Mood: neutral | Project: None + Tools: RAG, WEB, WEATHER, CODEBRAIN, POKERBRAIN + + ╭─ INTAKE SUMMARIES ──────────────────────────────────────────────── + │ L1 : Last message discussed philosophy... + │ L5 : Recent 5 messages covered existential topics... + │ L10 : Past 10 messages showed curiosity pattern... + ╰─────────────────────────────────────────────────────────────────── + + ╭─ RAG RESULTS (3) ────────────────────────────────────────────── + │ [1] 0.923 | Previous discussion about purpose... + │ [2] 0.891 | Note about existential philosophy... + │ [3] 0.867 | Memory of Viktor Frankl discussion... + ╰─────────────────────────────────────────────────────────────────── +──────────────────────────────────────────────────────────────────────────────────────────────────── + +🧠 Monologue | question | Tone: curious + +==================================================================================================== +✨ PIPELINE COMPLETE | Session: abc123 | Total: 1250ms +==================================================================================================== +⏱️ Stage Timings: + context : 150ms ( 12.0%) + identity : 10ms ( 0.8%) + monologue : 200ms ( 16.0%) + tools : 0ms ( 0.0%) + reflection : 50ms ( 4.0%) + reasoning : 450ms ( 36.0%) ← BOTTLENECK! + refinement : 300ms ( 24.0%) + persona : 140ms ( 11.2%) + learning : 50ms ( 4.0%) +📤 Output: 342 characters +==================================================================================================== +``` + +### Verbose Mode (Maximum Debug) +Same as detailed, plus: +- Full raw JSON responses from LLMs (50-line boxes) +- Complete intake data structures +- Stack traces on errors + +## 🚀 How to Use + +### For Finding Weak Links (Your Use Case) +```bash +# In .env: +LOG_DETAIL_LEVEL=detailed + +# Restart services: +docker-compose restart cortex relay +``` + +You'll now see: +- ✅ Which LLM backend is used +- ✅ What prompts are sent to each LLM +- ✅ What each LLM responds with +- ✅ Timing breakdown showing which stage is slow +- ✅ Context being used (RAG, intake summaries) +- ✅ Clean, hierarchical structure + +### For Production +```bash +LOG_DETAIL_LEVEL=summary +``` + +### For Deep Debugging +```bash +LOG_DETAIL_LEVEL=verbose +``` + +## 🔍 Finding Performance Bottlenecks + +With `detailed` mode, look for: + +1. **Slow stages in timing breakdown:** + ``` + reasoning : 3450ms ( 76.0%) ← THIS IS YOUR BOTTLENECK! + ``` + +2. **Backend failures:** + ``` + ⚠️ [LLM] PRIMARY failed | 14:23:45.234 | Connection timeout + ✅ [LLM] SECONDARY | Reply: Based on... ← Fell back to secondary + ``` + +3. **Loop detection:** + ``` + ⚠️ DUPLICATE MESSAGE DETECTED | Session: abc123 + 🔁 LOOP DETECTED - Returning cached context + ``` + +## 📁 Removed Features + +The following old logging features have been removed: + +- ❌ `VERBOSE_DEBUG` environment variable (replaced with `LOG_DETAIL_LEVEL`) +- ❌ File logging to `/app/logs/cortex_verbose_debug.log` (use `docker logs` instead) +- ❌ Separate verbose handlers in Python logging +- ❌ Per-module verbose flags + +## ✨ New Features + +- ✅ Single unified logging configuration +- ✅ Hierarchical, scannable output +- ✅ Collapsible data sections (boxes) +- ✅ Stage timing always shown in detailed mode +- ✅ Performance profiling built-in +- ✅ Loop detection and warnings +- ✅ Clean error formatting + +--- + +**The logging is now clean, concise, and gives you exactly what you need to find weak links!** 🎯 diff --git a/LOGGING_QUICK_REF.md b/LOGGING_QUICK_REF.md new file mode 100644 index 0000000..a0fb88c --- /dev/null +++ b/LOGGING_QUICK_REF.md @@ -0,0 +1,176 @@ +# Cortex Logging Quick Reference + +## 🎯 TL;DR + +**Finding weak links in the LLM chain?** +```bash +export LOG_DETAIL_LEVEL=detailed +export VERBOSE_DEBUG=true +``` + +**Production use?** +```bash +export LOG_DETAIL_LEVEL=summary +``` + +--- + +## 📊 Log Levels Comparison + +| Level | Output Lines/Message | Use Case | Raw LLM Output? | +|-------|---------------------|----------|-----------------| +| **minimal** | 1-2 | Silent production | ❌ No | +| **summary** | 5-7 | Production (DEFAULT) | ❌ No | +| **detailed** | 30-50 | Debugging, finding bottlenecks | ✅ Parsed only | +| **verbose** | 100+ | Deep debugging, seeing raw data | ✅ Full JSON | + +--- + +## 🔍 Common Debugging Tasks + +### See Raw LLM Outputs +```bash +export LOG_DETAIL_LEVEL=verbose +``` +Look for: +``` +╭─ RAW RESPONSE ──────────────────────────────────── +│ { "choices": [ { "message": { "content": "..." } } ] } +╰─────────────────────────────────────────────────── +``` + +### Find Performance Bottlenecks +```bash +export LOG_DETAIL_LEVEL=detailed +``` +Look for: +``` +⏱️ Stage Timings: + reasoning : 3450ms ( 76.0%) ← SLOW! +``` + +### Check Which RAG Memories Are Used +```bash +export LOG_DETAIL_LEVEL=detailed +``` +Look for: +``` +╭─ RAG RESULTS (5) ────────────────────────────── +│ [1] 0.923 | Memory content... +``` + +### Detect Loops +```bash +export ENABLE_DUPLICATE_DETECTION=true # (default) +``` +Look for: +``` +⚠️ DUPLICATE MESSAGE DETECTED +🔁 LOOP DETECTED - Returning cached context +``` + +### See All Backend Failures +```bash +export LOG_DETAIL_LEVEL=summary # or higher +``` +Look for: +``` +⚠️ [LLM] PRIMARY failed | Connection timeout +⚠️ [LLM] SECONDARY failed | Model not found +✅ [LLM] CLOUD | Reply: Based on... +``` + +--- + +## 🛠️ Environment Variables Cheat Sheet + +```bash +# Verbosity Control +LOG_DETAIL_LEVEL=detailed # minimal | summary | detailed | verbose +VERBOSE_DEBUG=false # true = maximum verbosity (legacy) + +# Raw Data Visibility +LOG_RAW_CONTEXT_DATA=false # Show full intake L1-L30 dumps + +# Loop Protection +ENABLE_DUPLICATE_DETECTION=true # Detect duplicate messages +MAX_MESSAGE_HISTORY=100 # Trim history after N messages +SESSION_TTL_HOURS=24 # Expire sessions after N hours + +# Features +NEOMEM_ENABLED=false # Enable long-term memory +ENABLE_AUTONOMOUS_TOOLS=true # Enable tool invocation +ENABLE_PROACTIVE_MONITORING=true # Enable suggestions +``` + +--- + +## 📋 Sample Output + +### Summary Mode (Default - Production) +``` +✅ [LLM] PRIMARY | 14:23:45.123 | Reply: Based on your question... +📊 Context | Session: abc123 | Messages: 42 | Last: 5.2min | RAG: 5 results +🧠 Monologue | question | Tone: curious +✨ PIPELINE COMPLETE | Session: abc123 | Total: 1250ms +📤 Output: 342 characters +``` + +### Detailed Mode (Debugging) +``` +════════════════════════════════════════════════════════════════════════════ +🚀 PIPELINE START | Session: abc123 | 14:23:45.123 +════════════════════════════════════════════════════════════════════════════ +📝 User: What is the meaning of life? +──────────────────────────────────────────────────────────────────────────── + +──────────────────────────────────────────────────────────────────────────── +🧠 LLM CALL | Backend: PRIMARY | 14:23:45.234 +──────────────────────────────────────────────────────────────────────────── +📝 Prompt: You are Lyra, a thoughtful AI assistant... +💬 Reply: Based on philosophical perspectives... + +📊 Context | Session: abc123 | Messages: 42 | Last: 5.2min | RAG: 5 results + ╭─ RAG RESULTS (5) ────────────────────────────── + │ [1] 0.923 | Previous philosophy discussion... + │ [2] 0.891 | Existential note... + ╰──────────────────────────────────────────────── + +════════════════════════════════════════════════════════════════════════════ +✨ PIPELINE COMPLETE | Session: abc123 | Total: 1250ms +════════════════════════════════════════════════════════════════════════════ +⏱️ Stage Timings: + context : 150ms ( 12.0%) + reasoning : 450ms ( 36.0%) ← Largest component + persona : 140ms ( 11.2%) +📤 Output: 342 characters +════════════════════════════════════════════════════════════════════════════ +``` + +--- + +## ⚡ Quick Troubleshooting + +| Symptom | Check | Fix | +|---------|-------|-----| +| **Logs too verbose** | Current level | Set `LOG_DETAIL_LEVEL=summary` | +| **Can't see LLM outputs** | Current level | Set `LOG_DETAIL_LEVEL=detailed` or `verbose` | +| **Repeating operations** | Loop warnings | Check for `🔁 LOOP DETECTED` messages | +| **Slow responses** | Stage timings | Look for stages >1000ms in detailed mode | +| **Missing RAG data** | NEOMEM_ENABLED | Set `NEOMEM_ENABLED=true` | +| **Out of memory** | Message history | Lower `MAX_MESSAGE_HISTORY` | + +--- + +## 📁 Key Files + +- **[.env.logging.example](.env.logging.example)** - Full configuration guide +- **[LOGGING_REFACTOR_SUMMARY.md](LOGGING_REFACTOR_SUMMARY.md)** - Detailed explanation +- **[cortex/utils/logging_utils.py](cortex/utils/logging_utils.py)** - Logging utilities +- **[cortex/context.py](cortex/context.py)** - Context + loop protection +- **[cortex/router.py](cortex/router.py)** - Pipeline stages +- **[core/relay/lib/llm.js](core/relay/lib/llm.js)** - LLM backend logging + +--- + +**Need more detail? See [LOGGING_REFACTOR_SUMMARY.md](LOGGING_REFACTOR_SUMMARY.md)** diff --git a/LOGGING_REFACTOR_SUMMARY.md b/LOGGING_REFACTOR_SUMMARY.md new file mode 100644 index 0000000..2b3c919 --- /dev/null +++ b/LOGGING_REFACTOR_SUMMARY.md @@ -0,0 +1,352 @@ +# Cortex Logging Refactor Summary + +## 🎯 Problem Statement + +The cortex chat loop had severe logging issues that made debugging impossible: + +1. **Massive verbosity**: 100+ log lines per chat message +2. **Raw LLM dumps**: Full JSON responses pretty-printed on every call (1000s of lines) +3. **Repeated data**: NeoMem results logged 71 times individually +4. **No structure**: Scattered emoji logs with no hierarchy +5. **Impossible to debug**: Couldn't tell if loops were happening or just verbose logging +6. **No loop protection**: Unbounded message history growth, no session cleanup, no duplicate detection + +## ✅ What Was Fixed + +### 1. **Structured Hierarchical Logging** + +**Before:** +``` +🔍 RAW LLM RESPONSE: { + "id": "chatcmpl-123", + "object": "chat.completion", + "created": 1234567890, + "model": "gpt-4", + "choices": [ + { + "index": 0, + "message": { + "role": "assistant", + "content": "Here is a very long response that goes on for hundreds of lines..." + } + } + ], + "usage": { + "prompt_tokens": 123, + "completion_tokens": 456, + "total_tokens": 579 + } +} +🧠 Trying backend: PRIMARY (http://localhost:8000) +✅ Success via PRIMARY +[STAGE 0] Collecting unified context... +[STAGE 0] Context collected - 5 RAG results +[COLLECT_CONTEXT] Intake data retrieved: +{ + "L1": [...], + "L5": [...], + "L10": {...}, + "L20": {...}, + "L30": {...} +} +[COLLECT_CONTEXT] NeoMem search returned 71 results + [1] Score: 0.923 - Memory content here... + [2] Score: 0.891 - More memory content... + [3] Score: 0.867 - Even more content... + ... (68 more lines) +``` + +**After (summary mode - DEFAULT):** +``` +✅ [LLM] PRIMARY | 14:23:45.123 | Reply: Based on your question about... +📊 Context | Session: abc123 | Messages: 42 | Last: 5.2min | RAG: 5 results +🧠 Monologue | question | Tone: curious +✨ PIPELINE COMPLETE | Session: abc123 | Total: 1250ms +📤 Output: 342 characters +``` + +**After (detailed mode - for debugging):** +``` +════════════════════════════════════════════════════════════════════════════════════════════════════ +🚀 PIPELINE START | Session: abc123 | 14:23:45.123 +════════════════════════════════════════════════════════════════════════════════════════════════════ +📝 User: What is the meaning of life? +──────────────────────────────────────────────────────────────────────────────────────────────────── + +──────────────────────────────────────────────────────────────────────────────────────────────────── +🧠 LLM CALL | Backend: PRIMARY | 14:23:45.234 +──────────────────────────────────────────────────────────────────────────────────────────────────── +📝 Prompt: You are Lyra, a thoughtful AI assistant... +💬 Reply: Based on philosophical perspectives, the meaning... + +📊 Context | Session: abc123 | Messages: 42 | Last: 5.2min | RAG: 5 results +──────────────────────────────────────────────────────────────────────────────────────────────────── +[CONTEXT] Session abc123 | User: What is the meaning of life? +──────────────────────────────────────────────────────────────────────────────────────────────────── + Mode: default | Mood: neutral | Project: None + Tools: RAG, WEB, WEATHER, CODEBRAIN, POKERBRAIN + + ╭─ INTAKE SUMMARIES ──────────────────────────────────────────────── + │ L1 : Last message discussed philosophy... + │ L5 : Recent 5 messages covered existential topics... + │ L10 : Past 10 messages showed curiosity pattern... + │ L20 : Session focused on deep questions... + │ L30 : Long-term trend shows philosophical interest... + ╰─────────────────────────────────────────────────────────────────── + + ╭─ RAG RESULTS (5) ────────────────────────────────────────────── + │ [1] 0.923 | Previous discussion about purpose and meaning... + │ [2] 0.891 | Note about existential philosophy... + │ [3] 0.867 | Memory of Viktor Frankl discussion... + │ [4] 0.834 | Reference to stoic philosophy... + │ [5] 0.801 | Buddhism and the middle path... + ╰─────────────────────────────────────────────────────────────────── +──────────────────────────────────────────────────────────────────────────────────────────────────── + +════════════════════════════════════════════════════════════════════════════════════════════════════ +✨ PIPELINE COMPLETE | Session: abc123 | Total: 1250ms +════════════════════════════════════════════════════════════════════════════════════════════════════ +⏱️ Stage Timings: + context : 150ms ( 12.0%) + identity : 10ms ( 0.8%) + monologue : 200ms ( 16.0%) + tools : 0ms ( 0.0%) + reflection : 50ms ( 4.0%) + reasoning : 450ms ( 36.0%) + refinement : 300ms ( 24.0%) + persona : 140ms ( 11.2%) +📤 Output: 342 characters +════════════════════════════════════════════════════════════════════════════════════════════════════ +``` + +### 2. **Configurable Verbosity Levels** + +Set via `LOG_DETAIL_LEVEL` environment variable: + +- **`minimal`**: Only errors and critical events +- **`summary`**: Stage completion + errors (DEFAULT - recommended for production) +- **`detailed`**: Include raw LLM outputs, RAG results, timing breakdowns (for debugging) +- **`verbose`**: Everything including full JSON dumps (for deep debugging) + +### 3. **Raw LLM Output Visibility** ✅ + +**You can now see raw LLM outputs clearly!** + +In `detailed` or `verbose` mode, LLM calls show: +- Backend used +- Prompt preview +- Parsed reply +- **Raw JSON response in collapsible format** (verbose only) + +``` +╭─ RAW RESPONSE ──────────────────────────────────────────────────────────────────────────── +│ { +│ "id": "chatcmpl-123", +│ "object": "chat.completion", +│ "model": "gpt-4", +│ "choices": [ +│ { +│ "message": { +│ "content": "Full response here..." +│ } +│ } +│ ] +│ } +╰─────────────────────────────────────────────────────────────────────────────────────────── +``` + +### 4. **Loop Detection & Protection** ✅ + +**New safety features:** + +- **Duplicate message detection**: Prevents processing the same message twice +- **Message history trimming**: Auto-trims to last 100 messages (configurable via `MAX_MESSAGE_HISTORY`) +- **Session TTL**: Auto-expires inactive sessions after 24 hours (configurable via `SESSION_TTL_HOURS`) +- **Hash-based detection**: Uses MD5 hash to detect exact duplicate messages + +**Example warning when loop detected:** +``` +⚠️ DUPLICATE MESSAGE DETECTED | Session: abc123 | Message: What is the meaning of life? +🔁 LOOP DETECTED - Returning cached context to prevent processing duplicate +``` + +### 5. **Performance Timing** ✅ + +In `detailed` mode, see exactly where time is spent: + +``` +⏱️ Stage Timings: + context : 150ms ( 12.0%) ← Context collection + identity : 10ms ( 0.8%) ← Identity loading + monologue : 200ms ( 16.0%) ← Inner monologue + tools : 0ms ( 0.0%) ← Autonomous tools + reflection : 50ms ( 4.0%) ← Reflection notes + reasoning : 450ms ( 36.0%) ← Main reasoning (BOTTLENECK) + refinement : 300ms ( 24.0%) ← Answer refinement + persona : 140ms ( 11.2%) ← Persona layer +``` + +**This helps you identify weak links in the chain!** + +## 📁 Files Modified + +### Core Changes + +1. **[llm.js](core/relay/lib/llm.js)** + - Removed massive JSON dump on line 53 + - Added structured logging with 4 verbosity levels + - Shows raw responses only in verbose mode (collapsible format) + - Tracks failed backends and shows summary on total failure + +2. **[context.py](cortex/context.py)** + - Condensed 71-line NeoMem loop to 5-line summary + - Removed repeated intake data dumps + - Added structured hierarchical logging with boxes + - Added duplicate message detection + - Added message history trimming + - Added session TTL and cleanup + +3. **[router.py](cortex/router.py)** + - Replaced 15+ stage logs with unified pipeline summary + - Added stage timing collection + - Shows performance breakdown in detailed mode + - Clean start/end markers with total duration + +### New Files + +4. **[utils/logging_utils.py](cortex/utils/logging_utils.py)** (NEW) + - Reusable structured logging utilities + - `PipelineLogger` class for hierarchical logging + - Collapsible data sections + - Stage timing tracking + - Future-ready for expansion + +5. **[.env.logging.example](.env.logging.example)** (NEW) + - Complete logging configuration guide + - Shows example output at each verbosity level + - Documents all environment variables + - Production-ready defaults + +6. **[LOGGING_REFACTOR_SUMMARY.md](LOGGING_REFACTOR_SUMMARY.md)** (THIS FILE) + +## 🚀 How to Use + +### For Finding Weak Links (Your Use Case) + +```bash +# Set in your .env or export: +export LOG_DETAIL_LEVEL=detailed +export VERBOSE_DEBUG=false # or true for even more detail + +# Now run your chat - you'll see: +# 1. Which LLM backend is used +# 2. Raw LLM outputs (in verbose mode) +# 3. Exact timing per stage +# 4. Which stage is taking longest +``` + +### For Production + +```bash +export LOG_DETAIL_LEVEL=summary + +# Minimal, clean logs: +# ✅ [LLM] PRIMARY | 14:23:45.123 | Reply: Based on your question... +# ✨ PIPELINE COMPLETE | Session: abc123 | Total: 1250ms +``` + +### For Deep Debugging + +```bash +export LOG_DETAIL_LEVEL=verbose +export LOG_RAW_CONTEXT_DATA=true + +# Shows EVERYTHING including full JSON dumps +``` + +## 🔍 Finding Weak Links - Quick Guide + +**Problem: "Which LLM stage is failing or producing bad output?"** + +1. Set `LOG_DETAIL_LEVEL=detailed` +2. Run a test conversation +3. Look for timing anomalies: + ``` + reasoning : 3450ms ( 76.0%) ← BOTTLENECK! + ``` +4. Look for errors: + ``` + ⚠️ Reflection failed: Connection timeout + ``` +5. Check raw LLM outputs (set `VERBOSE_DEBUG=true`): + ``` + ╭─ RAW RESPONSE ──────────────────────────────────── + │ { + │ "choices": [ + │ { "message": { "content": "..." } } + │ ] + │ } + ╰─────────────────────────────────────────────────── + ``` + +**Problem: "Is the loop repeating operations?"** + +1. Enable duplicate detection (on by default) +2. Look for loop warnings: + ``` + ⚠️ DUPLICATE MESSAGE DETECTED | Session: abc123 + 🔁 LOOP DETECTED - Returning cached context + ``` +3. Check stage timings - repeated stages will show up as duplicates + +**Problem: "Which RAG memories are being used?"** + +1. Set `LOG_DETAIL_LEVEL=detailed` +2. Look for RAG results box: + ``` + ╭─ RAG RESULTS (5) ────────────────────────────── + │ [1] 0.923 | Previous discussion about X... + │ [2] 0.891 | Note about Y... + ╰──────────────────────────────────────────────── + ``` + +## 📊 Environment Variables Reference + +| Variable | Default | Description | +|----------|---------|-------------| +| `LOG_DETAIL_LEVEL` | `summary` | Verbosity: minimal/summary/detailed/verbose | +| `VERBOSE_DEBUG` | `false` | Legacy flag for maximum verbosity | +| `LOG_RAW_CONTEXT_DATA` | `false` | Show full intake data dumps | +| `ENABLE_DUPLICATE_DETECTION` | `true` | Detect and prevent duplicate messages | +| `MAX_MESSAGE_HISTORY` | `100` | Max messages to keep per session | +| `SESSION_TTL_HOURS` | `24` | Auto-expire sessions after N hours | + +## 🎉 Results + +**Before:** 1000+ lines of logs per chat message, unreadable, couldn't identify issues + +**After (summary mode):** 5 lines of structured logs, clear and actionable + +**After (detailed mode):** ~50 lines with full visibility into each stage, timing, and raw outputs + +**Loop protection:** Automatic detection and prevention of duplicate processing + +**You can now:** +✅ See raw LLM outputs clearly (in detailed/verbose mode) +✅ Identify performance bottlenecks (stage timings) +✅ Detect loops and duplicates (automatic) +✅ Find failing stages (error markers) +✅ Scan logs quickly (hierarchical structure) +✅ Debug production issues (adjustable verbosity) + +## 🔧 Next Steps (Optional Improvements) + +1. **Structured JSON logging**: Output as JSON for log aggregation tools +2. **Log rotation**: Implement file rotation for verbose logs +3. **Metrics export**: Export stage timings to Prometheus/Grafana +4. **Error categorization**: Tag errors by type (network, timeout, parsing, etc.) +5. **Performance alerts**: Auto-alert when stages exceed thresholds + +--- + +**Happy debugging! You can now see what's actually happening in the cortex loop.** 🎯 diff --git a/README.md b/README.md index 0afc2b6..b8b1525 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,12 @@ -# Project Lyra - README v0.6.0 +# Project Lyra - README v0.7.0 Lyra is a modular persistent AI companion system with advanced reasoning capabilities and autonomous decision-making. It provides memory-backed chat using **Relay** + **Cortex** with integrated **Autonomy System**, featuring a multi-stage reasoning pipeline powered by HTTP-based LLM backends. -**Current Version:** v0.6.0 (2025-12-18) +**NEW in v0.7.0:** Standard Mode for simple chatbot functionality + UI backend selection + server-side session persistence + +**Current Version:** v0.7.0 (2025-12-21) > **Note:** As of v0.6.0, NeoMem is **disabled by default** while we work out integration hiccups in the pipeline. The autonomy system is being refined independently before full memory integration. @@ -25,14 +27,18 @@ Project Lyra operates as a **single docker-compose deployment** with multiple Do - Coordinates all module interactions - OpenAI-compatible endpoint: `POST /v1/chat/completions` - Internal endpoint: `POST /chat` -- Routes messages through Cortex reasoning pipeline +- Dual-mode routing: Standard Mode (simple chat) or Cortex Mode (full reasoning) +- Server-side session persistence with file-based storage +- Session management API: `GET/POST/PATCH/DELETE /sessions` - Manages async calls to Cortex ingest - *(NeoMem integration currently disabled in v0.6.0)* **2. UI** (Static HTML) - Browser-based chat interface with cyberpunk theme -- Connects to Relay -- Saves and loads sessions +- **NEW:** Mode selector (Standard/Cortex) in header +- **NEW:** Settings modal with backend selection and session management +- **NEW:** Light/Dark mode toggle (dark by default) +- Server-synced session management (persists across browsers and reboots) - OpenAI-compatible message format **3. NeoMem** (Python/FastAPI) - Port 7077 - **DISABLED IN v0.6.0** @@ -49,15 +55,22 @@ Project Lyra operates as a **single docker-compose deployment** with multiple Do - Primary reasoning engine with multi-stage pipeline and autonomy system - **Includes embedded Intake module** (no separate service as of v0.5.1) - **Integrated Autonomy System** (NEW in v0.6.0) - See Autonomy System section below -- **4-Stage Processing:** - 1. **Reflection** - Generates meta-awareness notes about conversation - 2. **Reasoning** - Creates initial draft answer using context - 3. **Refinement** - Polishes and improves the draft - 4. **Persona** - Applies Lyra's personality and speaking style +- **Dual Operating Modes:** + - **Standard Mode** (NEW in v0.7.0) - Simple chatbot with context retention + - Bypasses reflection, reasoning, refinement stages + - Direct LLM call with conversation history + - User-selectable backend (SECONDARY, OPENAI, or custom) + - Faster responses for coding and practical tasks + - **Cortex Mode** - Full 4-stage reasoning pipeline + 1. **Reflection** - Generates meta-awareness notes about conversation + 2. **Reasoning** - Creates initial draft answer using context + 3. **Refinement** - Polishes and improves the draft + 4. **Persona** - Applies Lyra's personality and speaking style - Integrates with Intake for short-term context via internal Python imports - Flexible LLM router supporting multiple backends via HTTP - **Endpoints:** - - `POST /reason` - Main reasoning pipeline + - `POST /reason` - Main reasoning pipeline (Cortex Mode) + - `POST /simple` - Direct LLM chat (Standard Mode) **NEW in v0.7.0** - `POST /ingest` - Receives conversation exchanges from Relay - `GET /health` - Service health check - `GET /debug/sessions` - Inspect in-memory SESSIONS state @@ -129,12 +142,38 @@ The autonomy system operates in coordinated layers, all maintaining state in `se --- -## Data Flow Architecture (v0.6.0) +## Data Flow Architecture (v0.7.0) -### Normal Message Flow: +### Standard Mode Flow (NEW in v0.7.0): ``` -User (UI) → POST /v1/chat/completions +User (UI) → POST /v1/chat/completions {mode: "standard", backend: "SECONDARY"} + ↓ +Relay (7078) + ↓ POST /simple +Cortex (7081) + ↓ (internal Python call) +Intake module → get_recent_messages() (last 20 messages) + ↓ +Direct LLM call (user-selected backend: SECONDARY/OPENAI/custom) + ↓ +Returns simple response to Relay + ↓ +Relay → POST /ingest (async) + ↓ +Cortex → add_exchange_internal() → SESSIONS buffer + ↓ +Relay → POST /sessions/:id (save session to file) + ↓ +Relay → UI (returns final response) + +Note: Bypasses reflection, reasoning, refinement, persona stages +``` + +### Cortex Mode Flow (Full Reasoning): + +``` +User (UI) → POST /v1/chat/completions {mode: "cortex"} ↓ Relay (7078) ↓ POST /reason @@ -158,11 +197,26 @@ Cortex → add_exchange_internal() → SESSIONS buffer ↓ Autonomy System → Update self_state.json (pattern tracking) ↓ +Relay → POST /sessions/:id (save session to file) + ↓ Relay → UI (returns final response) Note: NeoMem integration disabled in v0.6.0 ``` +### Session Persistence Flow (NEW in v0.7.0): + +``` +UI loads → GET /sessions → Relay → List all sessions from files → UI dropdown +User sends message → POST /sessions/:id → Relay → Save to sessions/*.json +User renames session → PATCH /sessions/:id/metadata → Relay → Update *.meta.json +User deletes session → DELETE /sessions/:id → Relay → Remove session files + +Sessions stored in: core/relay/sessions/ +- {sessionId}.json (conversation history) +- {sessionId}.meta.json (name, timestamps, metadata) +``` + ### Cortex 4-Stage Reasoning Pipeline: 1. **Reflection** (`reflection.py`) - Cloud LLM (OpenAI) @@ -196,6 +250,14 @@ Note: NeoMem integration disabled in v0.6.0 - OpenAI-compatible endpoint: `POST /v1/chat/completions` - Internal endpoint: `POST /chat` - Health check: `GET /_health` +- **NEW:** Dual-mode routing (Standard/Cortex) +- **NEW:** Server-side session persistence with CRUD API +- **NEW:** Session management endpoints: + - `GET /sessions` - List all sessions + - `GET /sessions/:id` - Retrieve session history + - `POST /sessions/:id` - Save session history + - `PATCH /sessions/:id/metadata` - Update session metadata + - `DELETE /sessions/:id` - Delete session - Async non-blocking calls to Cortex - Shared request handler for code reuse - Comprehensive error handling @@ -210,19 +272,35 @@ Note: NeoMem integration disabled in v0.6.0 **UI**: - Lightweight static HTML chat interface -- Cyberpunk theme -- Session save/load functionality +- Cyberpunk theme with light/dark mode toggle +- **NEW:** Mode selector (Standard/Cortex) in header +- **NEW:** Settings modal (⚙ button) with: + - Backend selection for Standard Mode (SECONDARY/OPENAI/custom) + - Session management (view, delete sessions) + - Theme toggle (dark mode default) +- **NEW:** Server-synced session management + - Sessions persist across browsers and reboots + - Rename sessions with custom names + - Delete sessions with confirmation + - Automatic session save on every message - OpenAI message format support ### Reasoning Layer -**Cortex** (v0.5.1): -- Multi-stage reasoning pipeline (reflection → reasoning → refine → persona) +**Cortex** (v0.7.0): +- **NEW:** Dual operating modes: + - **Standard Mode** - Simple chat with context (`/simple` endpoint) + - User-selectable backend (SECONDARY, OPENAI, or custom) + - Full conversation history via Intake integration + - Bypasses reasoning pipeline for faster responses + - **Cortex Mode** - Full reasoning pipeline (`/reason` endpoint) + - Multi-stage processing: reflection → reasoning → refine → persona + - Per-stage backend selection + - Autonomy system integration - Flexible LLM backend routing via HTTP -- Per-stage backend selection - Async processing throughout - Embedded Intake module for short-term context -- `/reason`, `/ingest`, `/health`, `/debug/sessions`, `/debug/summary` endpoints +- `/reason`, `/simple`, `/ingest`, `/health`, `/debug/sessions`, `/debug/summary` endpoints - Lenient error handling - never fails the chat pipeline **Intake** (Embedded Module): @@ -327,7 +405,28 @@ The following LLM backends are accessed via HTTP (not part of docker-compose): ## Version History -### v0.6.0 (2025-12-18) - Current Release +### v0.7.0 (2025-12-21) - Current Release +**Major Features: Standard Mode + Backend Selection + Session Persistence** +- ✅ Added Standard Mode for simple chatbot functionality +- ✅ UI mode selector (Standard/Cortex) in header +- ✅ Settings modal with backend selection for Standard Mode +- ✅ Server-side session persistence with file-based storage +- ✅ Session management UI (view, rename, delete sessions) +- ✅ Light/Dark mode toggle (dark by default) +- ✅ Context retention in Standard Mode via Intake integration +- ✅ Fixed modal positioning and z-index issues +- ✅ Cortex `/simple` endpoint for direct LLM calls +- ✅ Session CRUD API in Relay +- ✅ Full backward compatibility - Cortex Mode unchanged + +**Key Changes:** +- Standard Mode bypasses 6 of 7 reasoning stages for faster responses +- Sessions now sync across browsers and survive container restarts +- User can select SECONDARY (Ollama), OPENAI, or custom backend for Standard Mode +- Theme preference and backend selection persisted in localStorage +- Session files stored in `core/relay/sessions/` directory + +### v0.6.0 (2025-12-18) **Major Feature: Autonomy System (Phase 1, 2, and 2.5)** - ✅ Added autonomous decision-making framework - ✅ Implemented executive planning and goal-setting layer @@ -394,30 +493,39 @@ The following LLM backends are accessed via HTTP (not part of docker-compose): --- -## Known Issues (v0.6.0) +## Known Issues (v0.7.0) -### Temporarily Disabled (v0.6.0) +### Temporarily Disabled - **NeoMem disabled by default** - Being refined independently before full integration - PostgreSQL + pgvector storage inactive - Neo4j graph database inactive - Memory persistence endpoints not active - RAG service (Beta Lyrae) currently disabled in docker-compose.yml -### Non-Critical -- Session management endpoints not fully implemented in Relay -- Full autonomy system integration still being refined -- Memory retrieval integration pending NeoMem re-enablement +### Standard Mode Limitations +- No reflection, reasoning, or refinement stages (by design) +- DeepSeek R1 not recommended for Standard Mode (generates reasoning artifacts) +- No RAG integration (same as Cortex Mode - currently disabled) +- No NeoMem memory storage (same as Cortex Mode - currently disabled) + +### Session Management Limitations +- Sessions stored in container filesystem - requires volume mount for true persistence +- No session import/export functionality yet +- No session search or filtering +- Old localStorage sessions don't automatically migrate to server ### Operational Notes - **Single-worker constraint**: Cortex must run with single Uvicorn worker to maintain SESSIONS state - Multi-worker scaling requires migrating SESSIONS to Redis or shared storage - Diagnostic endpoints (`/debug/sessions`, `/debug/summary`) available for troubleshooting +- Backend selection only affects Standard Mode - Cortex Mode uses environment-configured backends ### Future Enhancements - Re-enable NeoMem integration after pipeline refinement - Full autonomy system maturation and optimization - Re-enable RAG service integration -- Implement full session persistence +- Session import/export functionality +- Session search and filtering UI - Migrate SESSIONS to Redis for multi-worker support - Add request correlation IDs for tracing - Comprehensive health checks across all services @@ -457,17 +565,56 @@ The following LLM backends are accessed via HTTP (not part of docker-compose): curl http://localhost:7077/health ``` -4. Access the UI at `http://localhost:7078` +4. Access the UI at `http://localhost:8081` + +### Using the UI + +**Mode Selection:** +- Use the **Mode** dropdown in the header to switch between: + - **Standard** - Simple chatbot for coding and practical tasks + - **Cortex** - Full reasoning pipeline with autonomy features + +**Settings Menu:** +1. Click the **⚙ Settings** button in the header +2. **Backend Selection** (Standard Mode only): + - Choose **SECONDARY** (Ollama/Qwen on 3090) - Fast, local + - Choose **OPENAI** (GPT-4o-mini) - Cloud-based, high quality + - Enter custom backend name for advanced configurations +3. **Session Management**: + - View all saved sessions with message counts and timestamps + - Click 🗑️ to delete unwanted sessions +4. **Theme Toggle**: + - Click **🌙 Dark Mode** or **☀️ Light Mode** to switch themes + +**Session Management:** +- Sessions automatically save on every message +- Use the **Session** dropdown to switch between sessions +- Click **➕ New** to create a new session +- Click **✏️ Rename** to rename the current session +- Sessions persist across browsers and container restarts ### Test -**Test Relay → Cortex pipeline:** +**Test Standard Mode:** ```bash curl -X POST http://localhost:7078/v1/chat/completions \ -H "Content-Type: application/json" \ -d '{ + "mode": "standard", + "backend": "SECONDARY", + "messages": [{"role": "user", "content": "Hello!"}], + "sessionId": "test" + }' +``` + +**Test Cortex Mode (Full Reasoning):** +```bash +curl -X POST http://localhost:7078/v1/chat/completions \ + -H "Content-Type: application/json" \ + -d '{ + "mode": "cortex", "messages": [{"role": "user", "content": "Hello Lyra!"}], - "session_id": "test" + "sessionId": "test" }' ``` @@ -492,6 +639,21 @@ curl http://localhost:7081/debug/sessions curl "http://localhost:7081/debug/summary?session_id=test" ``` +**List all sessions:** +```bash +curl http://localhost:7078/sessions +``` + +**Get session history:** +```bash +curl http://localhost:7078/sessions/sess-abc123 +``` + +**Delete a session:** +```bash +curl -X DELETE http://localhost:7078/sessions/sess-abc123 +``` + All backend databases (PostgreSQL and Neo4j) are automatically started as part of the docker-compose stack. --- @@ -515,12 +677,13 @@ OPENAI_API_KEY=sk-... **Module-specific backend selection:** ```bash -CORTEX_LLM=SECONDARY # Use Ollama for reasoning -INTAKE_LLM=PRIMARY # Use llama.cpp for summarization -SPEAK_LLM=OPENAI # Use OpenAI for persona -NEOMEM_LLM=PRIMARY # Use llama.cpp for memory -UI_LLM=OPENAI # Use OpenAI for UI -RELAY_LLM=PRIMARY # Use llama.cpp for relay +CORTEX_LLM=SECONDARY # Use Ollama for reasoning +INTAKE_LLM=PRIMARY # Use llama.cpp for summarization +SPEAK_LLM=OPENAI # Use OpenAI for persona +NEOMEM_LLM=PRIMARY # Use llama.cpp for memory +UI_LLM=OPENAI # Use OpenAI for UI +RELAY_LLM=PRIMARY # Use llama.cpp for relay +STANDARD_MODE_LLM=SECONDARY # Default backend for Standard Mode (NEW in v0.7.0) ``` ### Database Configuration @@ -541,6 +704,7 @@ NEO4J_PASSWORD=neomemgraph NEOMEM_API=http://neomem-api:7077 CORTEX_API=http://cortex:7081 CORTEX_REASON_URL=http://cortex:7081/reason +CORTEX_SIMPLE_URL=http://cortex:7081/simple # NEW in v0.7.0 CORTEX_INGEST_URL=http://cortex:7081/ingest RELAY_URL=http://relay:7078 ``` @@ -685,7 +849,10 @@ NeoMem is a derivative work based on Mem0 OSS (Apache 2.0). ### Debugging Tips - Enable verbose logging: `VERBOSE_DEBUG=true` in `.env` - Check Cortex logs: `docker logs cortex -f` +- Check Relay logs: `docker logs relay -f` - Inspect SESSIONS: `curl http://localhost:7081/debug/sessions` - Test summarization: `curl "http://localhost:7081/debug/summary?session_id=test"` -- Check Relay logs: `docker logs relay -f` +- List sessions: `curl http://localhost:7078/sessions` +- Test Standard Mode: `curl -X POST http://localhost:7078/v1/chat/completions -H "Content-Type: application/json" -d '{"mode":"standard","backend":"SECONDARY","messages":[{"role":"user","content":"test"}],"sessionId":"test"}'` - Monitor Docker network: `docker network inspect lyra_net` +- Check session files: `ls -la core/relay/sessions/` diff --git a/core/relay/lib/llm.js b/core/relay/lib/llm.js index 6e49927..01a382a 100644 --- a/core/relay/lib/llm.js +++ b/core/relay/lib/llm.js @@ -38,6 +38,8 @@ async function tryBackend(backend, messages) { // 🧩 Normalize replies let reply = ""; + let parsedData = null; + try { if (isOllama) { // Ollama sometimes returns NDJSON lines; merge them @@ -49,21 +51,75 @@ async function tryBackend(backend, messages) { .join(""); reply = merged.trim(); } else { - const data = JSON.parse(raw); - console.log("🔍 RAW LLM RESPONSE:", JSON.stringify(data, null, 2)); + parsedData = JSON.parse(raw); reply = - data?.choices?.[0]?.text?.trim() || - data?.choices?.[0]?.message?.content?.trim() || - data?.message?.content?.trim() || + parsedData?.choices?.[0]?.text?.trim() || + parsedData?.choices?.[0]?.message?.content?.trim() || + parsedData?.message?.content?.trim() || ""; - - } } catch (err) { reply = `[parse error: ${err.message}]`; } - return { reply, raw, backend: backend.key }; + return { reply, raw, parsedData, backend: backend.key }; +} + +// ------------------------------------ +// Structured logging helper +// ------------------------------------ +const LOG_DETAIL = process.env.LOG_DETAIL_LEVEL || "summary"; // minimal | summary | detailed | verbose + +function logLLMCall(backend, messages, result, error = null) { + const timestamp = new Date().toISOString().split('T')[1].slice(0, -1); + + if (error) { + // Always log errors + console.warn(`⚠️ [LLM] ${backend.key.toUpperCase()} failed | ${timestamp} | ${error.message}`); + return; + } + + // Success - log based on detail level + if (LOG_DETAIL === "minimal") { + return; // Don't log successful calls in minimal mode + } + + if (LOG_DETAIL === "summary") { + console.log(`✅ [LLM] ${backend.key.toUpperCase()} | ${timestamp} | Reply: ${result.reply.substring(0, 80)}...`); + return; + } + + // Detailed or verbose + console.log(`\n${'─'.repeat(100)}`); + console.log(`🧠 LLM CALL | Backend: ${backend.key.toUpperCase()} | ${timestamp}`); + console.log(`${'─'.repeat(100)}`); + + // Show prompt preview + const lastMsg = messages[messages.length - 1]; + const promptPreview = (lastMsg?.content || '').substring(0, 150); + console.log(`📝 Prompt: ${promptPreview}...`); + + // Show parsed reply + console.log(`💬 Reply: ${result.reply.substring(0, 200)}...`); + + // Show raw response only in verbose mode + if (LOG_DETAIL === "verbose" && result.parsedData) { + console.log(`\n╭─ RAW RESPONSE ────────────────────────────────────────────────────────────────────────────`); + const jsonStr = JSON.stringify(result.parsedData, null, 2); + const lines = jsonStr.split('\n'); + const maxLines = 50; + + lines.slice(0, maxLines).forEach(line => { + console.log(`│ ${line}`); + }); + + if (lines.length > maxLines) { + console.log(`│ ... (${lines.length - maxLines} more lines - check raw field for full response)`); + } + console.log(`╰${'─'.repeat(95)}`); + } + + console.log(`${'─'.repeat(100)}\n`); } // ------------------------------------ @@ -77,17 +133,29 @@ export async function callSpeechLLM(messages) { { key: "fallback", type: "llamacpp", url: process.env.LLM_FALLBACK_URL, model: process.env.LLM_FALLBACK_MODEL }, ]; + const failedBackends = []; + for (const b of backends) { if (!b.url || !b.model) continue; + try { - console.log(`🧠 Trying backend: ${b.key.toUpperCase()} (${b.url})`); const out = await tryBackend(b, messages); - console.log(`✅ Success via ${b.key.toUpperCase()}`); + logLLMCall(b, messages, out); return out; } catch (err) { - console.warn(`⚠️ ${b.key.toUpperCase()} failed: ${err.message}`); + logLLMCall(b, messages, null, err); + failedBackends.push({ backend: b.key, error: err.message }); } } + // All backends failed - log summary + console.error(`\n${'='.repeat(100)}`); + console.error(`🔴 ALL LLM BACKENDS FAILED`); + console.error(`${'='.repeat(100)}`); + failedBackends.forEach(({ backend, error }) => { + console.error(` ${backend.toUpperCase()}: ${error}`); + }); + console.error(`${'='.repeat(100)}\n`); + throw new Error("all_backends_failed"); } diff --git a/core/relay/server.js b/core/relay/server.js index c0e7c2a..1008cad 100644 --- a/core/relay/server.js +++ b/core/relay/server.js @@ -4,17 +4,26 @@ import express from "express"; import dotenv from "dotenv"; import cors from "cors"; +import fs from "fs/promises"; +import path from "path"; +import { fileURLToPath } from "url"; dotenv.config(); +// ES module __dirname workaround +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); +const SESSIONS_DIR = path.join(__dirname, "sessions"); + const app = express(); app.use(cors()); app.use(express.json()); const PORT = Number(process.env.PORT || 7078); -// Cortex endpoints (only these are used now) +// Cortex endpoints const CORTEX_REASON = process.env.CORTEX_REASON_URL || "http://cortex:7081/reason"; +const CORTEX_SIMPLE = process.env.CORTEX_SIMPLE_URL || "http://cortex:7081/simple"; // ----------------------------------------------------- // Helper request wrapper @@ -45,18 +54,32 @@ async function postJSON(url, data) { // ----------------------------------------------------- // The unified chat handler // ----------------------------------------------------- -async function handleChatRequest(session_id, user_msg) { +async function handleChatRequest(session_id, user_msg, mode = "cortex", backend = null) { let reason; - // 1. → Cortex.reason (main pipeline) + // Determine which endpoint to use based on mode + const endpoint = mode === "standard" ? CORTEX_SIMPLE : CORTEX_REASON; + const modeName = mode === "standard" ? "simple" : "reason"; + + console.log(`Relay → routing to Cortex.${modeName} (mode: ${mode}${backend ? `, backend: ${backend}` : ''})`); + + // Build request payload + const payload = { + session_id, + user_prompt: user_msg + }; + + // Add backend parameter if provided (only for standard mode) + if (backend && mode === "standard") { + payload.backend = backend; + } + + // Call appropriate Cortex endpoint try { - reason = await postJSON(CORTEX_REASON, { - session_id, - user_prompt: user_msg - }); + reason = await postJSON(endpoint, payload); } catch (e) { - console.error("Relay → Cortex.reason error:", e.message); - throw new Error(`cortex_reason_failed: ${e.message}`); + console.error(`Relay → Cortex.${modeName} error:`, e.message); + throw new Error(`cortex_${modeName}_failed: ${e.message}`); } // Correct persona field @@ -88,14 +111,16 @@ app.post("/v1/chat/completions", async (req, res) => { const messages = req.body.messages || []; const lastMessage = messages[messages.length - 1]; const user_msg = lastMessage?.content || ""; + const mode = req.body.mode || "cortex"; // Get mode from request, default to cortex + const backend = req.body.backend || null; // Get backend preference if (!user_msg) { return res.status(400).json({ error: "No message content provided" }); } - console.log(`Relay (v1) → received: "${user_msg}"`); + console.log(`Relay (v1) → received: "${user_msg}" [mode: ${mode}${backend ? `, backend: ${backend}` : ''}]`); - const result = await handleChatRequest(session_id, user_msg); + const result = await handleChatRequest(session_id, user_msg, mode, backend); res.json({ id: `chatcmpl-${Date.now()}`, @@ -136,10 +161,12 @@ app.post("/chat", async (req, res) => { try { const session_id = req.body.session_id || "default"; const user_msg = req.body.message || ""; + const mode = req.body.mode || "cortex"; // Get mode from request, default to cortex + const backend = req.body.backend || null; // Get backend preference - console.log(`Relay → received: "${user_msg}"`); + console.log(`Relay → received: "${user_msg}" [mode: ${mode}${backend ? `, backend: ${backend}` : ''}]`); - const result = await handleChatRequest(session_id, user_msg); + const result = await handleChatRequest(session_id, user_msg, mode, backend); res.json(result); } catch (err) { @@ -154,20 +181,182 @@ app.post("/chat", async (req, res) => { // ----------------------------------------------------- // SESSION ENDPOINTS (for UI) // ----------------------------------------------------- -// In-memory session storage (could be replaced with a database) -const sessions = new Map(); +// Helper functions for session persistence +async function ensureSessionsDir() { + try { + await fs.mkdir(SESSIONS_DIR, { recursive: true }); + } catch (err) { + console.error("Failed to create sessions directory:", err); + } +} -app.get("/sessions/:id", (req, res) => { +async function loadSession(sessionId) { + try { + const sessionPath = path.join(SESSIONS_DIR, `${sessionId}.json`); + const data = await fs.readFile(sessionPath, "utf-8"); + return JSON.parse(data); + } catch (err) { + // File doesn't exist or is invalid - return empty array + return []; + } +} + +async function saveSession(sessionId, history, metadata = {}) { + try { + await ensureSessionsDir(); + const sessionPath = path.join(SESSIONS_DIR, `${sessionId}.json`); + const metadataPath = path.join(SESSIONS_DIR, `${sessionId}.meta.json`); + + // Save history + await fs.writeFile(sessionPath, JSON.stringify(history, null, 2), "utf-8"); + + // Save metadata (name, etc.) + await fs.writeFile(metadataPath, JSON.stringify(metadata, null, 2), "utf-8"); + + return true; + } catch (err) { + console.error(`Failed to save session ${sessionId}:`, err); + return false; + } +} + +async function loadSessionMetadata(sessionId) { + try { + const metadataPath = path.join(SESSIONS_DIR, `${sessionId}.meta.json`); + const data = await fs.readFile(metadataPath, "utf-8"); + return JSON.parse(data); + } catch (err) { + // No metadata file, return default + return { name: sessionId }; + } +} + +async function saveSessionMetadata(sessionId, metadata) { + try { + await ensureSessionsDir(); + const metadataPath = path.join(SESSIONS_DIR, `${sessionId}.meta.json`); + await fs.writeFile(metadataPath, JSON.stringify(metadata, null, 2), "utf-8"); + return true; + } catch (err) { + console.error(`Failed to save metadata for ${sessionId}:`, err); + return false; + } +} + +async function listSessions() { + try { + await ensureSessionsDir(); + const files = await fs.readdir(SESSIONS_DIR); + const sessions = []; + + for (const file of files) { + if (file.endsWith(".json") && !file.endsWith(".meta.json")) { + const sessionId = file.replace(".json", ""); + const sessionPath = path.join(SESSIONS_DIR, file); + const stats = await fs.stat(sessionPath); + + // Try to read the session to get message count + let messageCount = 0; + try { + const data = await fs.readFile(sessionPath, "utf-8"); + const history = JSON.parse(data); + messageCount = history.length; + } catch (e) { + // Invalid JSON, skip + } + + // Load metadata (name) + const metadata = await loadSessionMetadata(sessionId); + + sessions.push({ + id: sessionId, + name: metadata.name || sessionId, + lastModified: stats.mtime, + messageCount + }); + } + } + + // Sort by last modified (newest first) + sessions.sort((a, b) => b.lastModified - a.lastModified); + return sessions; + } catch (err) { + console.error("Failed to list sessions:", err); + return []; + } +} + +async function deleteSession(sessionId) { + try { + const sessionPath = path.join(SESSIONS_DIR, `${sessionId}.json`); + const metadataPath = path.join(SESSIONS_DIR, `${sessionId}.meta.json`); + + // Delete session file + await fs.unlink(sessionPath); + + // Delete metadata file (if exists) + try { + await fs.unlink(metadataPath); + } catch (e) { + // Metadata file doesn't exist, that's ok + } + + return true; + } catch (err) { + console.error(`Failed to delete session ${sessionId}:`, err); + return false; + } +} + +// GET /sessions - List all sessions +app.get("/sessions", async (req, res) => { + const sessions = await listSessions(); + res.json(sessions); +}); + +// GET /sessions/:id - Get specific session history +app.get("/sessions/:id", async (req, res) => { const sessionId = req.params.id; - const history = sessions.get(sessionId) || []; + const history = await loadSession(sessionId); res.json(history); }); -app.post("/sessions/:id", (req, res) => { +// POST /sessions/:id - Save session history +app.post("/sessions/:id", async (req, res) => { const sessionId = req.params.id; const history = req.body; - sessions.set(sessionId, history); - res.json({ ok: true, saved: history.length }); + const success = await saveSession(sessionId, history); + + if (success) { + res.json({ ok: true, saved: history.length }); + } else { + res.status(500).json({ error: "Failed to save session" }); + } +}); + +// PATCH /sessions/:id/metadata - Update session metadata (name, etc.) +app.patch("/sessions/:id/metadata", async (req, res) => { + const sessionId = req.params.id; + const metadata = req.body; + const success = await saveSessionMetadata(sessionId, metadata); + + if (success) { + res.json({ ok: true, metadata }); + } else { + res.status(500).json({ error: "Failed to update metadata" }); + } +}); + +// DELETE /sessions/:id - Delete a session +app.delete("/sessions/:id", async (req, res) => { + const sessionId = req.params.id; + const success = await deleteSession(sessionId); + + if (success) { + res.json({ ok: true, deleted: sessionId }); + } else { + res.status(500).json({ error: "Failed to delete session" }); + } }); // ----------------------------------------------------- diff --git a/core/relay/sessions/sess-6rxu7eia.meta.json b/core/relay/sessions/sess-6rxu7eia.meta.json new file mode 100644 index 0000000..486dee3 --- /dev/null +++ b/core/relay/sessions/sess-6rxu7eia.meta.json @@ -0,0 +1,3 @@ +{ + "name": "My Coding Session" +} \ No newline at end of file diff --git a/core/relay/sessions/sess-dnm44wyb.json b/core/relay/sessions/sess-dnm44wyb.json new file mode 100644 index 0000000..d541f62 --- /dev/null +++ b/core/relay/sessions/sess-dnm44wyb.json @@ -0,0 +1,26 @@ +[ + { + "role": "user", + "content": "Hello! this is a new test session. Do you know who i am?" + }, + { + "role": "assistant", + "content": "Hi! In this session, I don't have information about your previous interactions. You can tell me who you are or any other details you'd like to share. How can I assist you today?" + }, + { + "role": "user", + "content": "sure im brian! i am designing you... you are a robot!" + }, + { + "role": "assistant", + "content": "Hello Brian! Nice to meet you. As an AI, I don't have physical design capabilities, but I'm here to help with any information or tasks you need. How can I assist you in your design process?" + }, + { + "role": "user", + "content": "Can you code python scripts for me?" + }, + { + "role": "assistant", + "content": "Sure thing, Brian! I can help you with Python scripting. What specifically do you need assistance with? Whether it's a simple script or something more complex, just let me know the details!" + } +] \ No newline at end of file diff --git a/core/relay/sessions/sess-dnm44wyb.meta.json b/core/relay/sessions/sess-dnm44wyb.meta.json new file mode 100644 index 0000000..9e26dfe --- /dev/null +++ b/core/relay/sessions/sess-dnm44wyb.meta.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/core/relay/sessions/sess-l08ndm60.meta.json b/core/relay/sessions/sess-l08ndm60.meta.json new file mode 100644 index 0000000..4305934 --- /dev/null +++ b/core/relay/sessions/sess-l08ndm60.meta.json @@ -0,0 +1 @@ +{"name":"Session 2"} diff --git a/core/ui/index.html b/core/ui/index.html index ca37a7b..0eb44bb 100644 --- a/core/ui/index.html +++ b/core/ui/index.html @@ -14,13 +14,14 @@