nexus-ai

Architecture

Design principles

  1. Slack is the identity provider. No separate auth system — workspace_id + slack_user_id scope every row. This eliminates an entire category of security risk (password storage, session hijacking) for free.
  2. Controllers are thin. Slack event/command handlers only parse input and call a service; all business logic lives in services/. This keeps the code testable without mocking the Slack SDK.
  3. One reasoning gateway. Every LLM call goes through services/groq.service.ts, so retry logic, logging, and (future) model-swapping happen in exactly one place.
  4. No premature infrastructure. No vector DB, no message queue, no Redis — the free-tier Supabase + a 60-second cron poll comfortably serves hackathon-to-early-startup scale (see docs/ROADMAP.md for the point at which each of these gets introduced).

System overview

flowchart LR
    subgraph Slack["Slack Workspace"]
        U[User] -->|slash command / DM / channel message| Bolt
    end

    subgraph Backend["NEXUS Backend (Node.js / TypeScript)"]
        Bolt[Slack Bolt App] --> Controllers
        Controllers --> Services
        Services --> Groq[Groq API\nLlama 3.3]
        Services --> Supabase[(Supabase\nPostgres)]
        Cron[node-cron jobs] --> Services
    end

    subgraph Frontend["Static Frontend (Vercel)"]
        Landing[Landing Page]
        Dashboard[Dashboard]
    end

    Dashboard -.->|future: authenticated REST API| Backend
    Bolt -->|ephemeral / DM messages| U

Core feature: proactive deadline detection

This is the feature judges will remember, so its data flow is documented in detail.

sequenceDiagram
    participant User
    participant Slack
    participant Bolt as Bolt App
    participant Deadline as deadline.service.ts
    participant Groq
    participant DB as Supabase

    User->>Slack: "Let's ship this by Friday"
    Slack->>Bolt: message.channels event
    Bolt->>Deadline: extractDeadline(text, today)
    Deadline->>Deadline: cheap regex pre-filter
    alt No temporal language
        Deadline-->>Bolt: isDeadline=false (skip Groq call)
    else Temporal language found
        Deadline->>Groq: structured JSON extraction prompt
        Groq-->>Deadline: {isDeadline, extractedTask, isoDate, confidence}
        Deadline-->>Bolt: result
        alt confidence >= 0.6
            Bolt->>Slack: postEphemeral "Add as task?" (visible only to author)
            User->>Slack: clicks "Add as task"
            Slack->>Bolt: block_actions payload
            Bolt->>DB: insert into tasks
            Bolt->>Slack: postEphemeral confirmation
        end
    end

Why an ephemeral prompt, not an automatic task: false positives are inevitable with NLP. An ephemeral, one-click confirmation keeps the human in the loop without adding friction — the opposite of a bot that silently fills your task list with noise, and the opposite of one that interrupts a channel publicly.

Meeting prep data flow

sequenceDiagram
    participant User
    participant Bolt
    participant MeetingPrep as meetingPrep.service.ts
    participant Memory as memory.service.ts
    participant Tasks as task.service.ts
    participant Groq

    User->>Bolt: /nexus prep "Acme renewal call"
    Bolt->>MeetingPrep: prepareMeeting(userId, title)
    par
        MeetingPrep->>Memory: searchMemory(title)
    and
        MeetingPrep->>Tasks: listTasks(status=open)
    end
    MeetingPrep->>Groq: synthesize talking points + follow-ups (JSON mode)
    Groq-->>MeetingPrep: {talkingPoints, followUps}
    MeetingPrep-->>Bolt: MeetingPrepResult
    Bolt->>User: formatted Slack message

Database schema (entity relationship)

erDiagram
    WORKSPACES ||--o{ USERS : has
    USERS ||--o{ TASKS : owns
    USERS ||--o{ REMINDERS : owns
    USERS ||--o{ MEMORY_ENTRIES : owns
    USERS ||--o{ CONVERSATION_HISTORY : owns

    WORKSPACES {
        uuid id PK
        text slack_team_id
        text team_name
    }
    USERS {
        uuid id PK
        uuid workspace_id FK
        text slack_user_id
        text display_name
    }
    TASKS {
        uuid id PK
        uuid user_id FK
        text title
        text status
        text priority
        timestamptz due_date
    }
    REMINDERS {
        uuid id PK
        uuid user_id FK
        text message
        timestamptz remind_at
        bool delivered
    }
    MEMORY_ENTRIES {
        uuid id PK
        uuid user_id FK
        text content
        text source_type
        text[] keywords
    }
    CONVERSATION_HISTORY {
        uuid id PK
        uuid user_id FK
        text role
        text content
    }

Full SQL: backend/src/database/schema.sql.

Deployment architecture

flowchart TB
    subgraph Local["Local Dev"]
        Dev[Developer] -->|Socket Mode, no public URL| SlackDev[Slack API]
    end

    subgraph Prod["Production"]
        SlackAPI[Slack API] -->|HTTPS events| Railway[Backend host\ne.g. Railway / Render free tier]
        Railway --> SupabaseProd[(Supabase Postgres)]
        Railway --> GroqAPI[Groq API]
        Vercel[Vercel: static frontend] -.->|future REST API| Railway
    end

We use Socket Mode for local development (no tunneling/ngrok needed) and switch to HTTP mode with a public URL only in production — controlled entirely by the SLACK_SOCKET_MODE env var, no code changes required.

Key technical risks & mitigations

Risk Mitigation
Groq API rate limits under load Central completeChat() wrapper with exponential backoff retry; cheap regex pre-filter avoids calling Groq on messages with no deadline signal
False-positive deadline detection annoying users Ephemeral (private) prompts only, confidence threshold, one click to dismiss
Supabase free-tier connection limits Single shared client (supabaseClient.ts), no per-request client creation
Conversation history growing unbounded trim_conversation_history Postgres function called after each turn, keeps last 40 messages/user
Slack token leakage Tokens only in .env (gitignored), never logged (logger.ts never receives raw tokens)
Reminder delivery drift Cron polls every minute with a 1-minute lookahead window — acceptable precision for a “remind me” feature, not a scheduling engine