ElijahOS
A portfolio that proves the pitch: a browser OS, a product surface, and an inspectable AI assistant built around evidence instead of slideware.
By Elijah Leung · Updated
Why it exists
A normal portfolio asks visitors to believe the claims on the page. ElijahOS makes them use the claims. The interface is a working surface: apps open, windows move, tools run, traces explain themselves, and the assistant can cite the same source material the visitor is reading.
That is the point of the project. Elijah comes from finance and operations, where useful systems are the ones that expose evidence, reduce ambiguity, and make the next decision easier. ElijahOS applies that standard to a personal site: if the promise is product judgment, AI systems, and shipping discipline, the proof should be interactive.
The result is part portfolio, part operating environment, and part systems demo. It shows how the shell, content model, AI assistant, observability, and fallback paths fit together instead of hiding the hard parts behind a marketing page.
What shipped
The desktop shell has a topbar, dock, launchpad, context menu, widget panel, and draggable windows with focus stacking, resize, minimize, maximize, snapping, tiling, and deep links. It behaves like a small OS because the interactions are the work sample.
The mobile shell is not a squeezed desktop. It has its own widget console, dock, app drawer, mobile app frames, app filtering, and routing behavior so the same portfolio feels intentional on a phone.
The app surface includes About, Projects, this case study, Resume, Contact, Ask Elijah, Lab, a zsh-style terminal, a puzzle-gated root window, calculator, clock, and Snake. Widgets cover weather, music, Wobbles media, and system pulse. Experiments are registered from source and surfaced through Lab instead of being one-off pages.
The production layer matters too: optimized media, generated icons, manifest, robots, sitemap, Open Graph image, rate limits, prompt-injection guards, optional persistence, and graceful disabled modes. Those details are not glamorous, but they are what let the demo stay usable — and safe — outside a local happy path.
Ask Elijah
Ask Elijah is the centerpiece because it turns the site into an AI product rather than a portfolio with a chat box attached. A visitor's turn runs the same path the eval harness exercises: clear the rate limiter and a deterministic guard, retrieve evidence, compose a grounded prompt, stream the model, resolve tools, continue once after server tool use, grade the answer, and emit a trace. The guard earns its place — prompt-injection and 'reveal your system prompt' attempts are refused in code, before a single token is spent.
Retrieval is hybrid by default. BM25 provides an inspectable sparse baseline. Dense search uses OpenAI text embeddings when an API key is available, Vercel Postgres pgvector when Postgres is configured, and an in-process vector store as the local fallback. The two channels are fused with Reciprocal Rank Fusion, then the trace records which channel contributed what.
Generation sits behind a provider abstraction instead of a hardcoded model. The answer comes from OpenAI's gpt-5.6-luna, picked because it handles this corpus at roughly a tenth of a frontier model's token price; Anthropic's claude-sonnet-5 is wired through the same interface as a confidence-gated escalation, so a draft that grades poorly is retried once against the stronger model instead of shipping. Generator, judge, embedder, and retriever are each chosen by environment, so the system can swap models without touching a call site — and every trace names the provider and model that actually answered.
The tool boundary is deliberate. Server tools can search and fetch knowledge, then feed that evidence back into the continuation turn. Client tools can operate the OS by opening apps, arranging windows, copying contact fields, showing media, and highlighting citations. The assistant is allowed to act, but the action is visible.
The trace and eval tabs are part of the product, not developer leftovers. Visitors can inspect used and rejected chunks, tool calls, retrieval metadata, token counts, latency, model choice, estimated cost, and confidence. Golden cases measure recall, answer format, and tool behavior against the same turn runner the live route uses.
Where it's heading
The next version is not about making the interface busier. It is about making the proof deeper: persistent traces beyond the current in-memory buffer, an optional reranker, an LLM judge beyond deterministic grading, richer experiment persistence, multimodal chunks, and a reverse channel where client tool results can feed back into the agent loop.
The case-study system is also meant to grow beyond ElijahOS. More flagship projects can earn this treatment once they have enough real product behavior, architecture, decisions, and results to be worth inspecting.
Architecture
The architecture is built around a simple loop: source-of-truth portfolio data becomes apps, retrieval chunks, embeddings, traces, and eval cases. The visitor sees the polished shell, but the case study exposes the system underneath it.
Browser shell
what visitors operate
- Desktop shell — topbar - dock - launchpad (Status: live)
- Mobile shell — widget console - dock - drawer (Status: live)
- Window system — Zustand - drag - resize - snap (Status: live)
- Widgets — weather - music - media - pulse (Status: live)
- Particle field — R3F - Three.js - GLSL (Status: live)
Product apps
the portfolio as software
- Ask Elijah — chat - trace - spec - evals (Status: in flight)
- Portfolio apps — about - projects - resume - contact (Status: live)
- Lab experiments — generated registry - deep links (Status: live)
- Terminal + root puzzle — virtual FS - encrypted pitch (Status: live)
- Mini apps — snake - clock - calculator (Status: live)
/api/ask/stream
agent loop
- 1 - Gate + validate request — rate limits - injection refusals (Status: live)
- 2 - Hybrid retrieve evidence — BM25 + dense + RRF (Status: live)
- 3 - Compose grounded prompt — server-only system prompt (Status: live)
- 4 - Stream + resolve tools — one continuation turn (Status: live)
- 5 - Grade, escalate, trace — deterministic judge (Status: live)
Retrieval & data
facts before fluency
- Typed source content — bio - projects - case study (Status: live)
- Manual KB chunks — positioning - philosophy - roadmap (Status: live)
- BM25 retriever — inspectable sparse baseline (Status: live)
- Embeddings — OpenAI text embeddings (Status: live)
- Vector stores — pgvector + in-process fallback (Status: live)
Generation & providers
model-agnostic by design
- Provider adapters — OpenAI + Anthropic SDKs (Status: live)
- Default model — OpenAI gpt-5.6-luna (Status: live)
- Confidence-gated fallback — Anthropic Sonnet + Haiku (Status: live)
- Swappable by env — generator - judge - embedder (Status: live)
Observability
proof surfaces
- Trace UI — chunks - tools - cost - confidence (Status: live)
- Eval report UI — golden cases (Status: live)
- Test suite — node:test - Playwright - UI contract (Status: live)
- Next roadmap — reranker - LLM judge - persistent traces (Status: planned)
The important pattern is degradation. The best path uses dense search, pgvector, and confidence-gated model escalation. Local or partially configured paths still answer with BM25, in-process vectors, and a single default model. Optional infrastructure and extra providers improve the product without making the demo brittle.
Decisions
- Desktop OS metaphor — The OS metaphor costs more to build, so it has to carry real weight. Here it turns navigation into evidence: visitors can inspect apps, move through tools, open traces, and see how the system behaves instead of only reading claims about it.
- Inspectable agent surface — Ask Elijah needed to prove judgment, not just produce fluent answers. Citations, tool traces, rejected chunks, confidence, cost, and evals make the assistant reviewable by the same visitor it is trying to help.
- BM25 baseline then hybrid retrieval — BM25 made the first system deterministic and easy to inspect. Once the corpus, embeddings, and eval cases were in place, hybrid retrieval added semantic recall without throwing away the sparse channel that makes failures understandable.
- Model-agnostic provider layer — Generation, grading, and embeddings each sit behind a small adapter chosen by environment. OpenAI answers by default, a weak grade can escalate the same turn to a stronger Anthropic model, and changing providers is a config edit instead of a refactor. The trace records which model actually answered, so the abstraction stays honest rather than decorative.
- Graceful optional infrastructure — The portfolio has to survive previews, local development, and missing secrets. Postgres, embeddings, leaderboards, and experiment persistence can improve the experience, but disabled modes and fallbacks keep the core product intact.
- Typed source content reused across surfaces — The same facts power project cards, app content, retrieval chunks, citations, and the case study. Keeping that content in typed source files makes it versioned, testable, diffable, and harder for the assistant to contradict.
- In-app traces and evals — For this product, observability is part of the user experience. The trace and eval tabs make the AI system's behavior visible where the claim is being made, which is more persuasive than a private dashboard nobody can inspect.
Stack
- Next.js 16 App Router — App shell, metadata, and route handlers Keeps the portfolio shell, APIs, streaming routes, and production metadata in one deployable app.
- React 19 — Interactive app runtime The OS metaphor depends on stateful, composable UI surfaces rather than static pages.
- React Three Fiber + Three.js + GLSL — WebGL particle field Adds a GPU-backed ambient layer while keeping the scene integrated with the React component model.
- Zustand — Window, widget, and Ask state Small client stores handle focus, drag, resize, app launch, widgets, traces, and chat state without heavier app framework machinery.
- Tailwind 4 + CSS tokens — Visual system The UI needs shared tokens plus plain CSS control for windows, apps, widgets, mobile, and case-study surfaces.
- OpenAI + Anthropic SDKs — Model-agnostic generation OpenAI gpt-5.6-luna answers by default; Anthropic claude-sonnet-5 is wired through the same interface as a confidence-gated escalation. Generator, judge, and embedder are all chosen by env, so swapping a model is a config change, not a rewrite — and the trace records which one actually answered.
- BM25 + Hybrid RRF retrieval — Sparse and dense evidence retrieval BM25 keeps matching inspectable; dense embeddings improve semantic recall; RRF fuses both without hiding the individual signals.
- OpenAI embeddings — Dense query and chunk vectors The semantic channel gives Ask Elijah a better shot at paraphrased questions that do not share exact keywords with the source.
- Vercel Postgres pgvector — Optional managed vector store Production can serve vector search from Postgres when configured, while local and preview paths retain fallbacks.
- SSE streaming — Chat response transport Visitors see the answer stream while traces, tool events, and completion metadata stay attached to the same turn.
- Per-IP + global rate limits — Abuse and cost guardrails A sliding per-IP window plus a per-instance circuit breaker keep the public assistant from being trivially drained, and deterministic refusals reject prompt-injection before the model is ever called.
- node:test + Playwright + evals — Verification suite Unit tests cover the runtime, Playwright checks responsive behavior, and golden evals measure the assistant's retrieval and tool behavior.