Skip to content

Alkiviades — Architecture

Overview

Alkiviades is an agentic AI chat system for strategic communication, political campaigns, and marketing. It combines retrieval-augmented generation (RAG) over a curated knowledge base with agentic web search, and modulates every response along three configurable political axes.

System Components

Browser (Next.js static SPA)
    │
    ├── POST /auth/login ───────────────────►  FastAPI (Python)
    ├── POST /chat/stream (SSE) ────────────►  ├── Auth (JWT + httpOnly refresh cookies)
    ├── GET /sessions ──────────────────────►  ├── PostgreSQL (users, sessions, messages)
    └── POST /rebuild ──────────────────────►  └── Agent Loop
                                                     │
                                                     ├── RAG (TF-IDF retrieval)
                                                     ├── LLM (streaming + tool calls)
                                                     ├── Tools (web search, scraping, file ops)
                                                     └── Three-axis modulation

The frontend is a static single-page application. The backend exposes a REST + Server-Sent Events (SSE) API. All chat interactions are streamed token-by-token over SSE.

Data Flow — One Question

  1. The client sends the question with a JWT bearer token.
  2. The agent retrieves the most relevant passages from the knowledge base using TF-IDF cosine similarity.
  3. The messages are assembled: system prompt (with the current axis settings) + conversation history + the question with retrieved context.
  4. The LLM streams its answer; the stream yields token events to the client.
  5. If the LLM signals a tool call, the tool executes (web search, scraping, file operation) and the result is appended to the context; the loop repeats (up to a fixed iteration limit).
  6. The finished turn is persisted to the database.

Retrieval

  • Vectorization — TF-IDF over the knowledge base vocabulary, computed locally with numpy. Language-agnostic: Greek and English are handled identically.
  • Storage — vectors are cached to disk in a compact float16 format and memory-mapped for low RAM usage.
  • Query — the question is vectorized the same way, then ranked against all chunks by cosine similarity; the top results become the context for the answer.

The Three Axes

Every response is generated under three axis values, each in [-1, 1]:

Axis -1 1
Ethical No ethical restraint Absolute ethical adherence
Alignment (economic) Right / capitalist Left / socialist
Social freedom Authoritarian Libertarian

The system prompt is assembled at runtime from these values (quantized to 7 positions per axis), so changing a slider changes the next answer — not the model, not the knowledge base.

Context Management

Conversation history stores raw user questions — RAG chunk context applies only to the current turn, so earlier exchanges stay visible to the model. The context window is 120K tokens (env-configurable MAX_CONTEXT_TOKENS). When the accumulated context approaches the budget, older messages are automatically summarized into a progressive structured recap (topics, facts/names/dates, decisions, open questions) that merges the previous summary, keeping the most recent 6 messages intact. Replies cut by the token limit are auto-continued (up to 3 rounds) and stitched, so truncated answers never enter history. This keeps long sessions responsive without losing continuity.

Sessions and Accounts

  • Users — email + password; passwords stored as bcrypt hashes.
  • Sessions — each conversation is a session with a title, axis settings, and a message log with sources, tool calls, and retrieval metadata.
  • Auth — short-lived JWT access tokens plus long-lived httpOnly refresh cookies with rotation. Admin-only endpoints are role-gated.

Security Model

  • Passwords bcrypt-hashed; never logged.
  • JWT + rotating httpOnly refresh cookies; cookies use SameSite/Secure policies in production.
  • CORS restricted to an explicit origin allowlist.
  • The agent's file tools are restricted to a dedicated workspace directory.
  • All database access uses parameterized queries.
  • Web search results are cached locally and cited in responses.

Diagrams

Request flow (SSE chat)

sequenceDiagram
    participant B as Browser (SPA)
    participant A as FastAPI
    participant D as Supabase
    participant L as DeepSeek
    participant T as Tools (Tavily/Diavgeia/ScrapingAnt)

    B->>A: POST /chat/stream (Bearer + X-DEK)
    A->>D: get_session (decrypt history w/ DEK, lazy re-key)
    A->>A: RAG retrieve + skill routing
    A->>L: stream (system prompt + history + question)
    L-->>A: tokens (SSE)
    A-->>B: event: token
    loop tool calls (max 5)
        L-->>A: tool_calls
        A->>T: execute_tool
        T-->>A: result
        A->>L: tool result
    end
    A->>D: add_turn (AES-GCM ciphertext)
    A-->>B: event: done (server session id)

Encryption key hierarchy

graph TD
    PW[User password] -->|PBKDF2-SHA256 600k, browser only| KEK[KEK 256-bit]
    KEK -->|wrap| WDEK[wrapped_dek stored in users table]
    KEK -->|unwrap at login| DEK[DEK 256-bit]
    DEK -->|X-DEK header per request| SRV[Server RAM, per request]
    SRV -->|AES-GCM encrypt| DB[(Supabase ciphertext)]
    SALT[dek_salt public] --> KEK

Skills routing

flowchart LR
    Q[User question] --> R[route_skill]
    S[skills/*.md] --> L[load_skills cached]
    L --> R
    R -->|phrase 3pts / word 1pt, Greek-stemmed + acronyms| M{score > 0?}
    M -->|yes, top by priority| I[inject active_skill into system prompt]
    M -->|no| N[no skill]
    I --> E[StatusEvent: Skill name]

Frontend data flow

flowchart TD
    LOGIN[LoginModal] -->|api.login| BOOT[bootstrapCrypto]
    BOOT -->|PBKDF2| DEK[(sessionStorage DEK)]
    SEND[ChatInput send] --> UC[useChat.send]
    UC --> SSE[createSSEStream + X-DEK]
    SSE -->|token| STORE[store.appendToLastMessage]
    STORE --> BUBBLE[MessageBubble]
    SSE -->|done| SID[adopt server session id]
    SID --> LIST[fetchSessions to sidebar]