agentleFS
pollinationspollinations/search.elixpo/CLAUDE.md

search.elixpo

CLAUDE.md24 starsChanged 7 months agoCC0-1.0View on GitHub

lixSearch is a multi-service intelligent search assistant (Python/Quart) that searches the web, fetches videos/images, and synthesizes answers with sources. It uses a pipeline architecture with distributed caching, vector embeddings via IPC, and Playwright-based search agents. Internal services communicate over a private Docker network (elixpo-network). The render service sits alone on a second network, render-network (172.31.250.0/24, bridge br-oreo-render), which only lixsearch-app also joins. None of the internal 9xxx ports are published to the host — they are only reachable within the…

  • Reads credentials

The file

We split this CLAUDE.md into the rules it gives an agent

3 rules. About 0 are common, 0 are distinctive, and 3 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈0

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 3

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Very specific:

    Config is centralized: all tunables live in pipeline/config.py with os.getenv() overrides.

    line 141
  2. Very specific:

    Shared volumes: All app containers mount the same conversations-data, embeddings-data, cache-data volumes and connect to the same Redis …

    line 145
  3. Very specific:

    All commits use a single author: Circuit-Overtime <ayushbhatt633@gmail.com>.

    line 271

≈ “Common” is an estimate: at least 1 in 5 of the 26,980 CLAUDE.md files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
CLAUDE.md

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

Project Summary

lixSearch is a multi-service intelligent search assistant (Python/Quart) that searches the web, fetches videos/images, and synthesizes answers with sources. It uses a pipeline architecture with distributed caching, vector embeddings via IPC, and Playwright-based search agents.

Build & Run

Docker (production)

./sops-reencrypt.sh --decrypt         # then fill in POLLINATIONS_API_KEY and optional HF_TOKEN
./deploy.sh build              # build image
./deploy.sh start 3            # start with 3 app containers
./deploy.sh scale 5            # scale to 5
./deploy.sh health             # check all services
./deploy.sh logs app           # tail app logs
./autoscale.sh                 # CPU-based autoscaler daemon (1-5 replicas)

Local development

source venv/bin/activate       # Python 3.11 venv at /mnt/volume_sfo2_01/lixSearch/venv/
redis-server --port 9530 &     # Redis on custom port
chroma run --host localhost --port 9001 &
APP_MODE=ipc python lixsearch/ipcService/main.py &      # embedding service (port 9510)
APP_MODE=worker WORKER_PORT=9002 python lixsearch/app/main.py &

Tests (integration, no unit test framework)

python tester/test_multi_turn_session.py
python tester/test_session_persistence.py
python tester/test_redis_semantic_cache.py

Health check

curl http://localhost:9002/api/health    # direct worker
curl http://localhost/api/health         # via nginx

Architecture

Network topology

Internal services communicate over a private Docker network (elixpo-network). The render service sits alone on a second network, render-network (172.31.250.0/24, bridge br-oreo-render), which only lixsearch-app also joins. None of the internal 9xxx ports are published to the host — they are only reachable within the Docker network (via expose). The only externally reachable entry point is nginx.

Internet → Cloudflare → search.elixpo.com
                              │
              ┌───────────────┴───────────────┐
              ▼                               ▼
     Next.js frontend (/)            nginx (:10001, API key)
     search.elixpo/                  ┌────────────────────────┐
     ├── /c/[session]               │  :80   internal/dev    │
     ├── /discover                  │  :10001 production     │
     ├── /library                   │  :443   reserved       │
     └── /api/* (proxy)             └──────────┬─────────────┘
              │                                │
              │  BACKEND_URL + X-Internal-Key   │ proxy_pass
              └───────────┬────────────────────┘
                          ▼
               lixsearch-app (:9002, N replicas, 10 Hypercorn workers each)
                 ├── Gateways (app/gateways/*.py)
                 ├── Pipeline (pipeline/lixsearch.py) — main async generator
                 │     ├── Tool execution (pipeline/optimized_tool_execution.py)
                 │     ├── RAG (ragService/)
                 │     └── LLM inference → Pollinations API (external)
                 ├── Session/cache (sessions/, ragService/cacheCoordinator.py)
                 ├── Render client (searching/scrape.py, X-Render-Token)
                 │     └─[render-network]→ render-service (:9520, singleton)
                 │           ├── Chromium, fresh context per render (screenshot/waitFor/fallback)
                 │           └── egress proxy (127.0.0.1) → public IPs only, ports 80/443
                 └── IPC client → ipc-service (:9510, singleton)
                                    ├── CoreEmbeddingService (sentence-transformers)
                                    ├── SearchAgentPool (Playwright text + image agents)
                                    └── Chroma vector DB (:9001)
Redis (:9530) shared across all containers:
  DB 0 — semantic query cache (5min TTL, per-session)
  DB 1 — URL embedding cache (24h TTL, global)
  DB 2 — session hot window (30min TTL, 20 msgs, LRU evicted to disk)

Ports & exposure

ServiceInternal portPublished to host?Purpose
nginx80, 443, 10001Yes — 80:80, 443:443, 10001:10001Only external entry point
lixsearch-app9002No (expose only)Quart API workers
ipc-service9510No (expose only)Embedding model + Playwright search agents
render-service9520No (expose only, render-network only)Scrape page rendering (Playwright behind egress proxy)
chroma-server9001No (expose only)Vector DB
redis9530No (expose only)Cache (3 DBs)

Authentication

  • Port 80 (nginx internal server): No API key required. Used for local dev and inter-service health checks.
  • Port 10001 (nginx authenticated server): API key required via X-API-Key header or ?key= query param. This is the production endpoint routed from search.elixpo.com through Cloudflare.
  • Auth-exempt paths (both ports): /api/health, /docs, /api/docs, /openapi.json, /openapi.yaml
  • App-level auth: INTERNAL_API_KEY env var in before_request middleware (app/main.py). Separate from the nginx API key — used for internal service-to-service calls.

Request flow

User → Cloudflare → nginx:10001 (API key check) → app:9002 → gateway (search.py / chat.py)
  → run_elixposearch_pipeline() async generator → query decomposition → tool routing
  → web_search/fetch/image_search/youtube → RAG retrieval → semantic cache check
  → LLM synthesis → SSE or JSON response

Conversation storage (two-tier hybrid)

  • Hot: Redis DB 2 — last 20 messages per session
  • Cold: Huffman-compressed .huff files in ./data/conversations/<session_id>.huff
  • LRU eviction daemon migrates idle sessions (30min) from Redis to disk
  • Disk archives have 30-day TTL, cleaned on startup

Key Files

FileRole
lixsearch/pipeline/lixsearch.pyMain pipeline orchestrator (~900 lines, async generator)
lixsearch/pipeline/config.pyAll configuration constants — edit here, not in service files
lixsearch/pipeline/optimized_tool_execution.pyTool routing engine
lixsearch/pipeline/tools.pyTool definitions (web_search, fetch_full_text, etc.)
lixsearch/pipeline/instruction.pySystem/user/synthesis prompts
lixsearch/app/main.pyQuart app with lifecycle hooks
lixsearch/app/gateways/HTTP handlers: search.py, chat.py, completions.py, session.py, health.py
lixsearch/app/gateways/completions.pyOpenAI-compatible /v1/chat/completions (primary Pollinations endpoint)
lixsearch/ragService/cacheCoordinator.pyOrchestrates all 3 Redis cache layers
lixsearch/ragService/semanticCacheRedis.pySessionContextWindow, SemanticCacheRedis, URLEmbeddingCache
lixsearch/sessions/hybrid_conversation_cache.pyTwo-tier Redis hot + disk cold cache
lixsearch/sessions/conversation_archive.pyHuffman-compressed disk persistence
lixsearch/sessions/huffman_codec.pyPure Python canonical Huffman codec
lixsearch/ipcService/searchPortManager.pySearch agent pool (Playwright browsers)
lixsearch/ipcService/coreEmbeddingService.pyEmbedding inference via sentence-transformers

Critical Patterns

  • session_id is the single identifier for all cross-service communication. It must be passed through memoized_results["session_id"] for CacheCoordinator and friends. Always snake_case, always str.
  • Config is centralized: all tunables live in pipeline/config.py with os.getenv() overrides. Don't hardcode values in service files.
  • Logging convention: loguru with [Section] prefixes — [Pipeline], [RAG], [Cache], [IPC], [POOL], [APP].
  • Pipeline yields strings: run_elixposearch_pipeline() is an async generator. Tools return strings that are yielded progressively to the client.
  • IPC is a singleton: The ipc-service container runs one instance shared by all app replicas. The SearchAgentPool inside it has configurable SEARCH_AGENT_POOL_SIZE and SEARCH_AGENT_MAX_TABS (in config.py).
  • Shared volumes: All app containers mount the same conversations-data, embeddings-data, cache-data volumes and connect to the same Redis instance. No per-container state.

Docker Services

ServiceReplicasPortPublished?Stateful?
nginx180, 443, 10001Yes (host-bound)no
lixsearch-app1-5 (autoscale)9002No (internal)no (shared volumes)
ipc-service19510No (internal)no
render-service19520No (render-network only)no
chroma-server19001No (internal)yes (embeddings-data)
redis19530No (internal)yes (redis-data)

Frontend (search.elixpo/)

Next.js app deployed on Cloudflare Pages at search.elixpo.com. Uses @cloudflare/next-on-pages for edge runtime.

Routes

RoutePurposeAuth required?
/Home — search inputNo
/c/[sessionId]Conversation viewNo (guest or logged-in)
/discoverDiscover feed (public)No
/discover/[category]Category feed (finance, academic, sports, patent, etc.)No
/librarySaved conversationsYes (logged-in users only)

Guest access

  • Guests (no login) get 15 free requests per IP (tracked in RATE_LIMIT_KV, 24h TTL)
  • Guest conversations navigate to /c/[sessionId] but are not saved to library
  • clientId is generated per browser (stored in localStorage as elixpo_client_id)
  • sessionId is generated per conversation (sess_ + 16 random chars)
  • Rate limit returns 429 with X-RateLimit-Remaining header

Frontend → Backend flow

Browser → Cloudflare Pages (edge, Next.js API routes)
           → validates XID header (anti-abuse token)
           → checks guest rate limit (RATE_LIMIT_KV)
           → reads/writes D1 for conversations, discover articles
           → proxies search/session/surf to backend via BACKEND_URL
           → uses X-Internal-Key header for backend auth

Frontend data layer

  • Cloudflare D1 (SQLite) — persistent storage for sessions, messages, bookmarks, discover articles
  • Cloudflare KV (SESSIONS_KV) — read cache for session data (10min TTL, invalidated on write)
  • Cloudflare KV (RATE_LIMIT_KV) — guest IP rate limiting (24h TTL)
  • Schema: workers/migrations/0001_init/schema.sql (base), 0002_users/schema.sql (users + auth)
  • Models: Session, Message, Bookmark, DiscoverArticle, User

Frontend API routes

RoutePurpose
api/searchProxies search queries to backend /api/search (SSE streaming, rate-limited)
api/sessionProxies session create/get to backend
api/conversationsLists/saves conversations (D1 direct)
api/conversations/[sessionId]Loads conversation messages (D1 + KV cache)
api/discoverFetches discover articles (D1 direct)
api/discover/generateGenerates articles via backend, saves to D1
api/surfProxies surf queries to backend
api/auth/loginRedirects to Elixpo Accounts SSO
api/auth/callbackOAuth callback — exchanges code, syncs user to D1, sets cookies
api/auth/meReturns current user (SSO + local profile merged)
api/auth/refreshRefreshes access token via refresh token cookie
api/auth/logoutClears auth cookies
api/user/profileGET/PATCH/DELETE user profile + preferences

Key frontend files

FileRole
src/lib/api.tsBackend URL, headers, XID validation
src/lib/auth.tsSSO OAuth flow, token exchange, cookie management
src/lib/db.tsD1 queries + KV cache + rate limiting + user CRUD
src/hooks/useSession.tsSession/clientId management
src/app/api/search/route.tsSearch proxy (SSE passthrough, rate-limited)
wrangler.tomlCloudflare Pages config (D1 + KV bindings)
workers/migrations/0001_init/schema.sqlD1 database schema
workers/migrations/0002_users/schema.sqlUser table + auth columns
env.d.tsCloudflare env type declarations

Deploy frontend

cd search.elixpo/
npm run pages:build            # build with @cloudflare/next-on-pages
npm run pages:deploy           # deploy to Cloudflare Pages
npm run db:migrate             # apply D1 migrations (production)
npm run db:migrate:local       # apply D1 migrations (local dev)

Cloudflare setup (one-time)

wrangler d1 create elixpo_search                    # create D1 database
wrangler kv namespace create elixpo_search_sessions     # create KV for session cache
wrangler kv namespace create elixpo_search_ratelimit    # create KV for rate limiting
# paste the IDs into wrangler.toml
wrangler secret put INTERNAL_API_KEY         # backend auth key
wrangler secret put XID                      # anti-abuse token
wrangler secret put API_KEY                  # nginx API key
wrangler secret put SSO_CLIENT_ID            # OAuth client ID from accounts.elixpo.com
wrangler secret put SSO_CLIENT_SECRET        # OAuth client secret from accounts.elixpo.com

SSO Authentication (Elixpo Accounts)

  • Provider: accounts.elixpo.com — OAuth 2.0 Authorization Code flow
  • Login: /api/auth/login redirects to SSO → callback exchanges code for tokens → sets httpOnly cookies
  • Cookies: elixpo_access_token (15min), elixpo_refresh_token (7d), both httpOnly/Secure/SameSite=Lax
  • User sync: On login, user profile is upserted from SSO into local D1 User table
  • Rate limiting: Authenticated users skip guest rate limit; guests get 15 requests/IP/24h
  • User profile: Local D1 stores bio, location, website, company, jobTitle, preferences (theme, language, searchRegion, safeSearch, deepSearchDefault), tier, usage stats
  • Register an OAuth app at accounts.elixpo.com/dashboard/oauth-apps with redirect URI https://search.elixpo.com/api/auth/callback

Environment Variables

Backend (.env.local)

Required: POLLINATIONS_API_KEY, RENDER_SERVICE_TOKEN (shared secret between lixsearch-app and render-service; if unset the render service refuses all requests and reports unhealthy); optional: HF_TOKEN Optional overrides: REDIS_HOST, REDIS_PORT, QDRANT_MODE, QDRANT_URL, QDRANT_PATH, QDRANT_COLLECTION, IPC_HOST, IPC_PORT, WORKERS (Hypercorn workers per container, default 10), WORKER_PORT (default 9002), LOG_LEVEL

Frontend (wrangler.toml vars + secrets)

Vars: BACKEND_URL, GUEST_REQUEST_LIMIT, SSO_REDIRECT_URI Secrets (via wrangler secret put): INTERNAL_API_KEY, XID, API_KEY, SSO_CLIENT_ID, SSO_CLIENT_SECRET

Commit Rule

  • All commits use a single author: Circuit-Overtime <ayushbhatt633@gmail.com>. Do not add Co-Authored-By or any other attribution trailers.

Render service isolation

lixsearch/renderService/ (APP_MODE=render) renders hostile pages for the scrape API. Layers:

  1. Network: attached only to render-network; no route to redis/qdrant/ipc/nginx. No env_file, cap_drop: ALL, no-new-privileges.
  2. Egress proxy (egress_proxy.py): Chromium runs with --proxy-server (loopback not bypassed, non-proxied UDP and QUIC disabled). The proxy resolves each target itself, requires every address to be public, and connects to that validated IP (no DNS rebinding). Ports limited by RENDER_PROXY_ALLOWED_PORTS.
  3. Host firewall: sudo ./deploy.sh firewall (idempotent, --dry-run to preview) adds OREO-RENDER-FWD (DOCKER-USER) and OREO-RENDER-IN (INPUT) chains dropping render traffic to private/link-local/CGNAT/multicast ranges, the host, and non-DNS UDP. Not persistent across reboots. Config lives under RENDER_* in pipeline/config.py.

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.