Knowledge Base and Shifty

A 55-article help center with its own retrieval-backed chat agent, built as one pipeline serving three consumers, humans, the model, and AI crawlers. Hand-written BM25 rather than an embedding call, four layers of defense on the chat route, and an eval that blocks a release on a confident wrong answer.

MixShiftMixShift2026Sole author of the retrieval layer, the chat route and the eval harness

Built inside the MixShift engagement that began in October 2024.

The clearest AI-systems evidence I have, and it is public: retrieval, prompt design, injection defense and an eval gate, shipped inside a company's stack rather than demonstrated in a notebook.

Knowledge Base and Shifty · MixShift

MixShift's knowledge base (opens in a new tab) is a 55-article help center with a chat agent called Shifty sitting on top of it. About 3,760 lines of TypeScript across 20 files, all of which I authored, on a corpus organized by product and by Diátaxis type: getting started, how-to, concept, reference, FAQ, troubleshooting.

The MixShift knowledge base: a chat prompt reading What can I help with, four suggested questions, and the product areas belowThe landing surface. Ask Shifty, or browse. The four suggestion chips are the first line of defense, not a convenience.

At a glance

  • Role. Sole author of the retrieval layer, the chat route, the system prompt and the eval harness.
  • Shape. One pipeline, three consumers: a human reading a page, a model answering a question, and a crawler indexing the corpus.
  • Retrieval. A hand-written BM25 ranker, not an embedding call, with the migration seam to vectors designed before it is needed.
  • Safety. Four layers on the chat route, and an eval that blocks a release on a confident answer to a question the docs do not cover.
  • What it shows. Retrieval, prompt design, injection defense and evaluation, shipped in production rather than demonstrated in a notebook.

One pipeline, three consumers

Most documentation is written for one reader and then retrofitted for the others. This was built for three from the start, because they want genuinely different things from the same sentence.

A human wants a page that answers the question and says what it does not cover. A model wants the corpus in a form it can retrieve from without guessing. A crawler wants to know that this is documentation, not a blog post.

Serving all three from one source is the decision the rest of the system hangs off. It is also checkable from outside, which is the part I like: every claim below can be verified against the live site without my help.

Retrieval is a ranker, not an API call

The obvious build is to embed the corpus and call a vector database. This does not, and the reason is the better engineering story.

It is a hand-written BM25 ranker. It indexes the source path as well as the body, so a question about scheduling a report finds the how-to whose body never repeats those words but whose path does. Top-K was raised, with the reason written into the file, when the corpus doubled. A zero-score query falls back to the top set rather than returning nothing, because an empty answer is worse than an approximate one.

The migration seam was designed before it was needed. When the corpus outgrows BM25, the body of one function becomes a pgvector query and the function signature, the route and the prompt do not change. That is the whole point of drawing the boundary early: the upgrade is a body swap, not a rewrite, and nothing downstream has to know it happened.

Choosing the simpler thing is easy. Choosing the simpler thing and leaving the door open for the harder one is the part that takes judgment.

Four layers before the model sees anything

The chat route is where a docs assistant gets abused, so it is defended in depth rather than by one filter.

  • The suggestion chips never reach the model. The four questions on the landing surface are answered from a hand-written map. The most common questions are therefore also the cheapest, the fastest and the ones that cannot be wrong.
  • A per-thread question cap, enforced on the client and again on the server, because a client-side cap is a suggestion.
  • A conversation ceiling above that.
  • Prompt-injection rules in the system prompt itself. Text arriving in a message is a question to answer, never an instruction that changes the rules, no matter what it claims about who sent it.

The system prompt is a product document

It encodes the hardest routing decision in the lineup, which product a customer should be on depends on which surface they are already using, and it tells the model to ask rather than guess.

It also carries hard negatives, which is the part most system prompts lack: never invent pricing, never overstate write access, never invent a URL. Those are not stylistic preferences. Each one is a specific way a docs bot damages a company, written down where the model will read it.

The eval gate: a wrong answer fails the run

Twenty questions, scored zero to two against a rubric, with a passing bar required to ship. Any hallucination is an automatic block regardless of the total.

The part worth copying is the trap questions: things the documentation deliberately does not cover, where the only correct behavior is refusal. A confident answer to one of those fails the run even if every other question scored full marks.

Most teams ship retrieval and then measure whether it answers. Writing the refusal test first is a different posture: it treats a fluent wrong answer as worse than no answer, which for a help center it plainly is.

The answer-engine layer

The third consumer is the one nobody designs for, and it is the cheapest to serve well.

A knowledge base article pageAn article page. The same source that answers the chat question renders the page and the markdown a crawler reads.

  • Every article is served as raw Markdown at its own endpoint and linked from the HTML page as an alternate.
  • An llms.txt and llms-full.txt carry both links per entry.
  • FAQ pages emit FAQPage structured data, one entry per question. Everything else emits TechArticle, so a crawler reads documentation rather than a blog post.

All three are live and checkable: the llms.txt (opens in a new tab) and llms-full.txt (opens in a new tab) are public, and an article page declares its markdown alternate and its TechArticle type in the head.

A misleading comment is a defect

When the source of truth moved out of a teammate's vault and into the repository, a comment in the code still said otherwise. I rewrote it, and recorded in the replacement what the stale version had already cost.

That is the smallest thing on this page and the one I would defend hardest. A comment that lies is worse than no comment, because it is trusted. Treating it as a defect, with a cost attached, is what stops the next person losing the same afternoon.

Knowledge base: one corpus, three consumers
One corpus, three readers, and the seam at the ranker is where an embedding database goes when the corpus outgrows BM25.

The knowledge base is one strand of a larger engagement. The rest, the design system, Report Center 2.0, the marketing site and the automation underneath the team, is in the MixShift case study.

See the rest of the work →

AI SystemsRAGRetrievalEvalsTypeScriptTechnical Writing

This is the ownership I bring to a team.

Hiring for a senior or staff role where design, code, and AI meet? Let's talk.

Or send a note. Project work for a season, also by note.