Skip to content

Alkiviades — Architecture

Overview

Alkiviades is an agentic AI chat system for strategic communication, political campaigns, and marketing. It combines RAG (Retrieval-Augmented Generation) over political documents and strategy theory with web search, modulated by three configurable political axes.

System Architecture

Browser (Next.js SPA)
    ├── GET /health ────────────────────────►
    ├── POST /auth/login ───────────────────►  FastAPI (Uvicorn)
    ├── POST /chat/stream (SSE) ────────────►  ├── Auth (JWT + bcrypt + httpOnly cookies)
    ├── GET /sessions ──────────────────────►  ├── SQLite (users, sessions, messages, refresh_tokens)
    └── POST /rebuild ──────────────────────►  └── Agent Loop
                                                     ├── RAG (TF-IDF over 2,461 chunks)
                                                     │   ├── Embedder (numpy float16, vocab cap 15K, mmap)
                                                     │   ├── KnowledgeBase (cosine similarity, pickle cache)
                                                     │   └── Source: processed/ (Greek docs) + stratcom/ (English)
                                                     ├── LLM (DeepSeek v4 Pro)
                                                     │   └── Streaming via httpx + non-streaming retry for tools
                                                     ├── Tools
                                                     │   ├── search_news (Tavily API)
                                                     │   ├── scrape_url (ScrapingAnt)
                                                     │   ├── load_search (cached results)
                                                     │   └── file ops (read/write/edit/append)
                                                     └── Three-Axis Modulation
                                                         ├── Ethical (-1 unscrupulous to +1 principled)
                                                         ├── Economic Alignment (-1 right to +1 left)
                                                         └── Social Freedom (-1 authoritarian to +1 libertarian)

Data Flow

Chat Request (SSE Stream)

  1. Client sends POST /chat/stream with JWT Bearer token
  2. Agent retrieves top-5 chunks via TF-IDF cosine similarity
  3. Messages assembled: [system_prompt, history, user_with_context]
  4. LLM streaming call with tools — yields TokenEvents via SSE
  5. If tool_calls detected: non-streaming retry to get tool params → execute tools → results appended → re-stream
  6. Max 5 tool iterations, then forced final response
  7. Session turn saved to SQLite

Auth Flow

  1. POST /auth/login → bcrypt password verify → JWT access token (15min) + httpOnly refresh cookie (7 days)
  2. All protected endpoints validate JWT via Authorization: Bearer <token> or access_token cookie
  3. On 401: client calls POST /auth/refresh with refresh cookie → new tokens, old refresh revoked
  4. Refresh token rotation: old refresh token revoked before new one issued

History Trimming

When total tokens > MAX_CONTEXT_TOKENS (48K), oldest messages summarized via lightweight LLM call. Last SUMMARY_KEEP_LAST_N (6) messages kept intact.

Deployment Architecture

alkiviades.cc ───────── Cloudflare Pages (Next.js static export)
api.alkiviades.cc ──── Render (Docker container, free tier 512MB)
   │                       ├── Python 3.12 + FastAPI + Uvicorn
   │                       ├── Knowledge base pre-built at deploy time
   │                       ├── Memory: float16 vectors + vocab cap 15K + mmap (~75MB)
   │                       └── SQLite (ephemeral, resets on deploy)
DNS ────────────────── Cloudflare (CNAME records)

Key Design Decisions

Decision Rationale
TF-IDF over ML embeddings Zero external deps, language-agnostic (Greek + English works), fast local retrieval
numpy float16 + mmap Fits 2,461 chunks into Render free tier 512MB RAM (down from ~300MB float32)
Vocabulary cap at 15K terms Removes rare/infrequent terms, further memory reduction
DeepSeek v4 Pro High quality, affordable, OpenAI-compatible API
SQLite for sessions Single-file, zero setup, sufficient for single-server deployment
JWT + httpOnly refresh cookies Stateless auth with secure token rotation
SSE streaming Real-time token-by-token response, heartbeat for connection keepalive
Static frontend export Cloudflare Pages compatible, CDN-cached, zero server-side rendering needed
Direct bcrypt (no passlib) passlib incompatible with bcrypt 5.x
pickle for vectors Simpler than Chroma/FAISS, mmap-compatible, no extra deps

Three-Axis Modulation

The system prompt (backend/app/prompt.txt) contains {ethical}, {alignment}, and {social} placeholders. These are interpolated at runtime from the AXES dict in config.py. The axes are configurable via the UI (sliders) or API (PUT /api/axes).

Sliders are quantized to 7 discrete positions: -1 (far left), -0.67 (left), -0.33 (center-left), 0 (center), +0.33 (center-right), +0.67 (right), +1 (far right)

Module Dependency Graph

main.py ─────► agent.py ──► utils.py ──► stream_events.py
   │               │            │
   ├── auth.py     ├── rag.py   ├── config.py
   ├── database.py ├── embed.py ├── models.py
   ├── session.py  ├── prompt.py
   └── logging_config.py └── tools.py ──► files.py

No circular imports. models.py imports from stream_events.py for type definitions.

Security

  • Passwords: bcrypt with 72-byte truncation
  • JWT: HS256, 15-min access, 7-day refresh with rotation
  • Cookies: httpOnly, configurable SameSite/Secure/Domain
  • CORS: Whitelist of allowed origins via env var
  • File ops: Path restricted to sessions/ directory
  • Tool execution: Thread-pool isolated, error-return pattern
  • SQL: Parameterized queries (never f-strings)
  • Admin endpoints: Role-gated via require_admin dependency
  • Guest accounts: Limited to 5 queries, auto-decrement