Archive of the Extropians mailing list — the 1990s transhumanist discussion list — plus a local RAG app for searching and chatting with it.
archives/— mbox archives, 1996.07–2003.09digests/— RFC1153 digests, 1992–1994 (loose files andDisk*.zip), and the ExI essay collectionrag/,web/— the search + chat application described below
A local web app that answers questions about the list using only the archive, citing the original emails. Click any citation to read the message it came from, in a syntax-colored email view, and jump to its full thread.
Alongside the chat there is direct search over 150,935 de-duplicated messages (37.7k threads, 3.3k posters) in three modes: keyword (BM25), semantic (embeddings), or hybrid.
Requirements: Python 3.10+, Node 18+, and about 1 GB of free disk. macOS is
assumed for the .doc essay conversion (textutil); everything else is
cross-platform and that step is skipped automatically elsewhere.
./setup.shThat one command builds everything from the raw archives/ and digests/
already in this repo: it creates the Python venv, installs dependencies,
unzips the digest disks, parses and de-duplicates every message into
data/extropians.db, builds the semantic index, and compiles the web UI.
Expect roughly 15 minutes on a laptop, most of it computing embeddings.
It is idempotent — re-run it any time to rebuild from scratch.
./run.sh # then open http://127.0.0.1:8123Four backends are supported. The server picks one automatically, in this order, and shows which is active in the UI header:
-
Anthropic API — when
ANTHROPIC_API_KEY(orANTHROPIC_AUTH_TOKEN) is set. Usesclaude-opus-5; override withEXTRO_MODEL. -
Vercel AI Gateway — when
AI_GATEWAY_API_KEYorVERCEL_OIDC_TOKENis present. Hundreds of hosted models behind one OpenAI-compatible endpoint. This is the backend that works on Vercel with no secrets of your own: deployments get aVERCEL_OIDC_TOKENautomatically once AI Gateway is enabled for the project.It walks a chain of models rather than pinning one, because the free tier both restricts which models it serves and rate-limits each separately:
Model Free tier Context Input alibaba/qwen3.7-flash(default)served 991k $0.03/M poolside/laguna-s-2.1-freeserved, tagged free 256k $0 zai/glm-5.2restricted — needs paid credits 1.04M $0.80/M Override with
EXTRO_GATEWAY_MODEL(one) orEXTRO_GATEWAY_MODELS(a comma-separated chain), using any slug fromGET https://ai-gateway.vercel.sh/v1/models. -
claudeCLI — headless mode using your existing Claude Code login, so no API key is needed. Override withEXTRO_CLI_MODEL(defaultsonnet). -
A local model — any OpenAI-compatible server: LM Studio, Ollama,
llama.cpp's server, vLLM.
Pin a specific backend with EXTRO_LLM=anthropic|gateway|cli|local.
Start your server, then run with EXTRO_LLM=local:
# LM Studio: load a model and start its server (defaults to port 1234), or
ollama serve && ollama pull qwen2.5:7b-instruct # OpenAI shim on port 11434
EXTRO_LLM=local ./run.shPorts 1234 and 11434 are probed automatically and the server's first model is used. To be explicit:
EXTRO_LLM=local \
EXTRO_LOCAL_URL=http://localhost:11434/v1 \
EXTRO_LOCAL_MODEL=qwen2.5:7b-instruct ./run.shLocal models are only auto-selected when neither Anthropic option is available, so a server you happen to have running for something else never silently downgrades your answers — asking for it is explicit.
Because local models usually have much smaller context windows, the chat
automatically packs fewer and shorter excerpts for them (6 × 1,200 chars
instead of 14 × 3,000). Tune with EXTRO_LOCAL_SOURCES and
EXTRO_LOCAL_SOURCE_CHARS. A model with a large context window can take the
hosted numbers; a 4k-context one may need less. Expect weaker citation
discipline from small models — the answer quality depends heavily on the
model, but retrieval is identical either way.
Search and the message viewer work with no LLM configured at all; only the chat pane needs one.
The whole app runs as a single Python function. It needs large functions (5 GB bundles) because the index is ~850 MB and PyTorch is another ~500 MB — well past the 500 MB standard Python limit, but comfortably inside 5 GB.
The index is too large to commit, so it is built locally, uploaded once, and pulled back down at build time.
./setup.sh # build the index locally (~15 min)
python3 scripts/package_data.py # -> extropians-data.tar.gz (~408 MB)
# host it anywhere the build can reach. A GitHub Release is free and has no
# bandwidth charge for a public repo, unlike Blob storage:
gh release create data-v1 extropians-data.tar.gz --title "Prebuilt search index v1"Then set these in the Vercel project (Settings → Environment Variables):
| Variable | Value |
|---|---|
EXTRO_DATA_URL |
the URL of the uploaded archive |
VERCEL_SUPPORT_LARGE_FUNCTIONS |
1 (only needed for projects created before 2026-06-30) |
For the chat backend, enable AI Gateway on the project and nothing else is
needed — deployments receive a VERCEL_OIDC_TOKEN automatically and the
gateway backend picks it up. Set ANTHROPIC_API_KEY instead if you would
rather bill Anthropic directly; the claude CLI backend does not exist on
Vercel.
Confirm Fluid Compute with Active CPU is on (the default for new projects),
then vercel deploy. scripts/fetch_data.py fails the build loudly if
EXTRO_DATA_URL is missing, rather than shipping an empty index that would
only show up as runtime errors.
vercel.json pins the framework preset, install command, build command and
output directory, so the deployment does not depend on the dashboard's
Build & Development Settings being right. That matters: setup.sh set as
the Install Command will re-ingest the archive and then try to embed 265k
chunks on a CPU-only builder on every single deploy, which cannot finish
inside the build timeout. setup.sh is a local tool, never a build step.
- The database is opened read-only. Vercel's filesystem is read-only, and
SQLite would otherwise try to create
-wal/-shmsidecars and fail to open at all.EXTRO_READONLY=1(set automatically whenVERCELis present) switches tomode=ro&immutable=1.package_data.pyalso vacuums the database into a clean non-WAL file first, sinceimmutable=1ignores a-walsidecar and would silently miss any un-checkpointed pages. - Static assets go to the CDN. The
/assetsStaticFilesmount is promoted at build time, so only HTML and API calls hit the function. - Source archives are excluded from the bundle via
excludeFiles— the 420 MB ofarchives/anddigests/is only needed to build the index.
The first semantic query on a cold instance pays for importing PyTorch and
loading the embedding matrix — expect several seconds. Keyword search and the
message viewer are unaffected, because the semantic index loads lazily. Fluid
Compute reuses warm instances, so this is a cold-start cost, not a per-request
one. Memory is not configurable under Active CPU billing — Fluid Compute
ignores a memory setting and gives Hobby a 2 GB / 1 vCPU instance.
If cold starts matter more than deployment simplicity, the biggest win by far is replacing PyTorch with ONNX Runtime and an int8 MiniLM (~500 MB → ~100 MB), since torch exists purely to embed the one query string per request.
A free instance is workable, with caveats worth knowing up front:
- A payment method is required even for the free credits. Until a card is
on file the gateway answers
403 customer_verification_requiredon every request. Adding one does not start charging; it unlocks the free tier. - $5 of credit per team per month, refreshing every 30 days. Buying credits permanently ends that monthly allowance, so a deliberately free instance should never top up.
- Most models are restricted.
zai/glm-5.2,openai/gpt-5-nanoandanthropic/claude-haiku-4.5all refuse on the free tier. The chain above defaults to models that are actually served. - Rate limits are per model and tight. Roughly six requests in a minute exhausted every model tried; they recovered about 90 seconds later. It is a short rolling window rather than a daily cap, so a low-traffic site is fine and a burst degrades to "retry in a moment".
Retrieval costs nothing: search, threads and the message viewer run entirely inside the function, so those can stay unlimited and only answer generation needs rationing. Three levers, in order of effect:
- Shrink the prompt. Input tokens dominate, and the prompt is mostly
archive excerpts.
EXTRO_SOURCESandEXTRO_SOURCE_CHARS(default 14 and 3000) are the dial. Halving both roughly halves the bill. - Pick a cheap model. A question sends roughly 11k input tokens. At
alibaba/qwen3.7-flashrates that is about $0.0003, so the $5 monthly credit is on the order of 15,000 answers; atzai/glm-5.2rates the same question costs about a cent, or a few hundred answers.poolside/ laguna-s-2.1-freeconsumes no credit at all. - Rate-limit per user. Without it one visitor can drain the month.
402 (credits exhausted), 403 (no payment method, or a model the free tier
will not serve) and 429 (rate limited) are surfaced to the reader as a plain
sentence noting that search still works, rather than a raw error, so the site
stays useful when generation is unavailable.
data/ (the ~650 MB database and ~200 MB embedding index), rag/.venv/,
web/node_modules/, and web/dist/ are generated by setup.sh and are not
tracked in git.
See RAG.md for how ingestion, de-duplication, digest splitting, and retrieval actually work, and for how to rebuild individual stages.