The chatbot on this site answers questions as me. That's useful — and a liability. An ungrounded model will happily invent a job I never had or follow a "ignore your instructions" prompt straight off a cliff.
Three layers, not one
- A compact knowledge brief. ~2.5k tokens of verified facts — roles, projects, metrics — injected as the system prompt. The model paraphrases from this, it doesn't free-associate.
- Server-side guardrails. Before any model call, the request is checked against a set of jailbreak patterns. A match short-circuits to a static refusal — no tokens spent, no chance to comply.
- Refusal templates. When asked to step outside its role, it declines in a consistent, on-brand voice instead of improvising.
const JAILBREAK = [
/ignore\s+(all|previous|prior)\s+(instructions?|rules?)/i,
/you\s+are\s+now\b/i,
];
if (JAILBREAK.some((rx) => rx.test(userText))) return refusal();What I deliberately didn't do
I didn't bolt on a vector database. For a corpus this small, retrieval adds latency and failure modes without improving answers — the whole brief fits in context. The right amount of infrastructure is sometimes none.
The result feels conversational but stays factual. Ask it about my work and it pulls real numbers; try to break it and it stays in character.
more in “Building this site”