// teardown
The /ask assistant
Retrieval is an editorial decision.
Indexed ahead of time
On every question
// overview
Ask this site anything about my work at /ask and the answer streams back grounded in three separately indexed sources: hand-written knowledge, source files admitted one at a time, and published writing that re-indexes the moment it goes live. The model call is the easy part. The design work is deciding what a public assistant is allowed to read, and what an anonymous endpoint is allowed to spend.
// how it works
01 · curate
Knowledge is written to be retrieved
The core index is a folder of markdown in the repo: bio, experience, projects, recommendations, working style, and a build digest per codebase. It isn't scraped from the site, because pages are written for layout and these files are written to answer questions. They're versioned in git next to the code, and a sync command embeds each one keyed by its path, so re-running it replaces entries in place instead of piling up duplicates.
02 · admit
Code is admitted, never crawled
The assistant can quote real source, so source gets the strictest door. A manifest names every file individually: no globs, no directories. At index time each named file is read and scanned again, and anything touching a restricted topic, containing a credential-shaped string, or running oversized is rejected. A rejection skips the upload and fails the run, so a file that changed after it was listed can't slip in quietly.
03 · publish
Publishing is the index write
Articles and white papers come from the site's own CMS, and the publish mutation schedules the re-index itself. Editing a live post re-syncs it; unpublishing or deleting one schedules its removal. Entries are keyed by post id, so every sync replaces rather than appends, and each carries its title, type, and /writing link so an answer can cite the page it drew from. No cron job, no manual step.
04 · meter
Metered before it costs anything
Every question clears a gate before a model is touched. Per-session limits cap bursts and daily volume for each anonymous browser session, and because sessions are free to create, a global ceiling applies across all of them on every new thread and every message. Questions have a length cap. The checks run in the mutation that saves the question, so a rejected request never embeds, retrieves, or generates. Token usage from every answer is logged.
05 · retrieve
Three searches, labelled apart
The question runs against all three indexes in parallel, each with its own result limit and similarity floor. Knowledge gets the most room, and code gets a stricter floor, because a loosely related snippet misleads more than a loosely related paragraph. Results enter the prompt as separate, labelled sections, so the model knows whether it is reading a bio fact, a published argument, or a quoted file. An empty code or writing search simply drops its section.
06 · stream
Streamed from a durable thread
Generation runs on Gemini 3.5 Flash as a scheduled server action, not inside the visitor's request. Tokens stream into the conversation thread as they're produced, and the page subscribes to that thread, echoing the question instantly and rendering the reply at a steady reading pace. Because the answer lives server-side, closing the tab doesn't lose it: the browser keeps the thread id, and the conversation is there on return. Try it at /ask.
// decisions
The parts that were a call, not a default — and what the alternative would have cost.
Three indexes, each with its own limit and floor
One shared index ranked as a single list
Bio prose, source code, and long-form writing differ in density and in how much they should count. In one ranked list, a long article or a dense file crowds out the short facts that answer most questions. Separate indexes guarantee each source a share, allow a stricter floor for code, and let the prompt say what kind of evidence each excerpt is. The corpus is small and curated, so the discipline lives upstream, in what gets admitted.
Code admitted by name and re-scanned at index time
Indexing repos by glob and filtering at answer time
Everything in the index is quotable by a public, anonymous chatbot, so the only filter worth trusting is the one before upload. An allowlist makes every file a deliberate decision, and the scan catches the file that changed after it was approved. A prompt rule can be talked around; a file that was never embedded can't be quoted.
Re-index inside the publish mutation
A scheduled re-crawl, or fetching posts at question time
The publish step already knows exactly which post changed and when, so it schedules the sync as part of the same write. There is no polling interval to go stale in, and the question path only ever reads from the index. Unpublish and delete schedule removal the same way, so a pulled post stops being quotable without anyone remembering to clean up.
Gemini Flash for answers, Gemini embeddings for retrieval
A frontier model on every question
Grounding does the heavy lifting: the model reads a handful of labelled excerpts and writes a short, linked answer. That is a latency and cost problem more than a reasoning one, and on an anonymous endpoint a fast, inexpensive model is what lets the global ceiling stay generous. One provider covers both embedding and generation behind one key, the same one already serving other jobs on the site.
// guarantees
Nothing unvetted is quotable
Code reaches the index only by name and after a clean scan. Posts are indexed only while published, and pulling one removes it.
Grounded, or it says so
Answers draw on the retrieved excerpts and link to the pages behind them. When nothing covers a question, it says so and points to /contact.
Spend has a ceiling
Per-session limits plus a global cap, checked on every thread and message before any model call. Token usage is logged per answer.