GORGES
Case study  ·  Live on the Great Energy site, password-protected beta

Ask Daniela: a decade of teaching, finally answerable

For Great Energy, one of our own companies, we built 'Ask Daniela,' a private assistant that answers only from one teacher's own body of work, with a second teacher's material already ingested and held in its own separate knowledge base. Great Energy is a part-time education business Christopher built with his wife Daniela, so this ran in our own shop first. Its only source of knowledge is that teacher's own transcripts, articles, and notes, so it answers from her real work or it does not answer at all. It is live now on the Great Energy site.

Private beta, password protected. Ask us for access and we will walk you through it live.
The problem

Ten-plus years of material, locked in unsearchable files

Great Energy has two teachers and more than a decade of teaching behind it, and a body of knowledge that existed only as hundreds of raw class transcripts, hand-written articles and course manuals scattered across project folders, and a curated bank of stories and quotes that had never been surfaced anywhere. Tens of thousands of files, in every format from word-processor bundles to meeting-call captions. None of it searchable. No way for a student, a prospective client, or even the teachers themselves to ask 'what have we said about this?' and get an answer grounded in their actual work.

The before and after. A decade of teaching, locked in files no one could search, becomes: ask a real question in your own words, get an answer grounded only in that teacher's material, plus a hand-picked story or quote that makes it land.
What it looks like

One assistant per teacher, on purpose

Great Energy's two teachers cover different material for different people. A single 'search everything' tool would blur two distinct voices, so each teacher gets their own assistant, drawing on their own knowledge base and nothing else. Ask Daniela is live on the Great Energy site; the qigong teacher's knowledge base is already ingested and isolated, with its assistant to follow. The exchanges below are representative previews.

Q
Ask the qigong teacherKnowledge base ingested and isolated, assistant to follow
I'm brand new to qigong. How do I build a daily practice that actually sticks?

Start smaller than you think you need to. Five to ten minutes, at the same time each day, matters far more than a long session you can only manage twice a week. The body learns the rhythm from repetition, not from intensity.

Pick one simple standing posture and one slow breath to return to, and let that be the whole practice for the first couple of weeks. Once it feels like something you look forward to rather than a task, you add to it. Consistency first, depth second.

D
Ask DanielaLive on the Great Energy site, grounded only in Daniela's own work
How do I tell whether my fatigue might be tied to an autoimmune issue?

A few patterns are worth noticing: fatigue that does not lift with rest, that comes with joint aches, digestive changes, or flares that track with stress or certain foods. On their own none of these confirms anything, and the pattern over weeks tells you more than any single day.

The next step is testing with a practitioner who reads the full picture, not one marker in isolation. What this work adds is the daily inputs, sleep, food, and stress, that move the pattern one way or the other while you get to the bottom of it.

Representative previews. Example questions and answers are mocked to show the experience; they are not quotes pulled from anyone's real material.

How it is built

More than a wrapper around one API call

Four independently-deployed pieces, each doing one job. The embeddings and reranking run on Cloudflare's own network, and Claude writes the final answer.

5,618
source files scanned
2,691
indexed after content review
71,110
vectors in the live index
2
isolated knowledge bases, one per teacher
The actual Ask Daniela workflow in n8n, both lanes. Lane A: student portal chat into a safety gate into a Knowledge Agent on Claude Sonnet 5, with Postgres-backed memory, a forced reasoning step, a two-stage Cloudflare RAG search, and a curated story and quote channel. Lane B: the same portal and safety gate into a fixed pipeline that loads history, always runs both searches, and makes one call to gpt-oss 120B on Cloudflare to write the answer.
The actual workflow, both lanes. Top: Lane A, where a Claude Sonnet 5 agent holds the tools and decides what to fetch, with Postgres-backed memory and a forced reasoning step that checks the draft against the brand-voice rules. Bottom: Lane B, where a fixed pipeline behind the open-weights gpt-oss 120B always runs both searches and writes the answer in one call. Both share the same safety gate and the same knowledge base. The two lanes are explained in full just below.
1 · Ingestion

Python pipeline

Walks human-reviewed source folders, extracts text from mixed formats, parses speaker-labeled transcripts, chunks with overlap, embeds every chunk.

.pages / PDF / RTFOtter.ai · WEBVTTspeaker-awareincremental re-runs
→
2 · Retrieval

Cloudflare

The vector database, the embedding and reranking models, and a custom search API, all on one platform.

VectorizeWorkers AI, embeddingsWorkers AI, reranka custom Worker
→
3 · Orchestration

n8n + Claude

A chat workflow per assistant: a forced think step, the search Worker called as a tool to ground the answer, Claude writing it, and memory across turns.

Claude Sonnet 5agent looptool callSupabase Postgres memory
→
4 · Front end

Embedded on the site

The assistant is embedded on Great Energy's existing Squarespace site, on a password-protected page, styled to match, with access locked to its domains.

Squarespace embedpassword-protectedCORS restricted
Two isolated knowledge bases. One teacher's assistant literally cannot retrieve the other's content, enforced at the vector-database level through namespace isolation, not by prompting. And a second, parallel search pulls only from the curated story and quote banks, 232 hand-written stories and 123 curated quotes, so every answer can be paired with a genuine, hand-picked story, the way a real teacher answers and then offers a story that makes it land.
Two-stage retrieval, and safety at ingestion. A first-pass vector search over-fetches candidates, then a dedicated reranking model re-scores them against the question before the top few are used. Content safety is built into ingestion, not bolted on: the pipeline reads each file to identify it rather than trusting its extension, and excluded a mislabeled sensitive file that a name-based filter would have let through.

The debugging journey is part of the credibility: vector-ID collisions were caught and fixed, and the content-safety net caught a mislabeled sensitive file by reading it rather than trusting its name. The embeddings and reranking run on Cloudflare, the vector database and the search API live there too, so the retrieval path sits in one platform, one bill, one place to watch cost and latency. What the primary assistant does with that retrieval, and the second way we run the same knowledge base, is the next section.

Two lanes, one knowledge base

The same assistant, run two ways

The same assistant runs twice over the same knowledge base, with the same voice rules and the same retrieval. Only the model and how much agency it is given change. One lane lets a frontier model decide what to look up; the other runs a fixed pipeline behind an open-weights model. Running both answers the question every prospective client actually has, which is whether they need to pay frontier-model prices.

Lane A · Claude Sonnet 5 agent
Student portal→ Safety gate→ Claude Sonnet 5 agent decides what to fetch, about 4 model calls per reply
↕
Shared, identical for both lanes
Cloudflare WorkerVectorize, 2 namespacesstory and quote channelSupabase memory
↕
Lane B · gpt-oss 120B, open weights on Cloudflare
Student portal→ Safety gate→ search→ search→ gpt-oss 120B fixed pipeline, no tool calling, 1 model call

Only the model and its agency differ. Everything in the middle is identical, so when the two answers differ, nothing else can be responsible.

Inside the shared retrieval

1 · Embed

bge-large-en-v1.5

The question is embedded into a 1024-dimensional vector.

→
2 · Recall

Vectorize

Twenty candidates come back, filtered by namespace and content type.

→
3 · Rerank

bge-reranker-base

The candidates are re-scored against the actual question.

→
4 · Top 5

To the model

The five best chunks go to whichever model is running that lane.

Frontier model or open weights, side by side

Lane A, Claude Sonnet 5Lane B, gpt-oss 120B
Who decides what to retrieveThe modelThe pipeline
Model calls per replyAbout 41
Where it runsAnthropicCloudflare, open weights
Conversation memoryYesYes
Citations shownPromptedDeterministic, built from what was retrieved
Answer qualityBest. Finds her real stories and places them wellGood. Occasionally looser with counts

Why the second lane has no agent

Lane B is a fixed pipeline by necessity, not by taste. No agent can work against Cloudflare's OpenAI-compatible endpoint, and it took direct API calls to isolate why.

RequestResult
tools array present200
Forced tool_choice200
tool_choice: required200
Assistant tool_calls plus a tool result in the conversation400

An agent asks for its final answer through a tool call, so that call lands in the history and the next request fails. Two different models failed in two different ways before the cause was found. Taking agency away from the model removes the entire failure class, which is exactly what Lane B does.

Sharper, not just bigger

A smaller index, on purpose

The index started at 237,328 vectors and is now 71,110. That is not a system that shrank by accident. A folder of personal notes that should never have been ingested was purged, a third-party book was removed, and low-value fragments were filtered out. A smaller, cleaner index answers better than a big one full of noise.

Original index237,328
Live index now71,110
What changedVectors
Personal notes removed (see the security audit below)-154,540
Third-party book removed-360
Low-value fragments filtered out-11,318
Live index now71,110

Cleaner chunks, measured

We measured the cleanup on an 80-chunk sample. The share of chunks carrying junk that hurts retrieval dropped to near zero.

Bare URLs
Before7.2%
After0%
Tabs inside prose
Before3.9%
After0%
Raw HTML markup
Before3.8%
After0%
Image embeds
Before2.8%
After0%
Unusable fragments
Before4.6%
After1.2%
How we know it works

Built to be measured, and to fail safe

Every control on this system exists because something failed first. That is more honest than a feature list, and it is what a careful buyer wants to see.

An evaluation harness, because retrieval and answers fail differently. Thirty-eight cases measure retrieval quality and answer quality separately. Retrieval is model-independent, so the numbers stay comparable when the model changes. Every run is recorded to a database with its git commit, so quality is a trend, not a snapshot. It caught real defects on its first run.
A deterministic safety gate, because a prompt rule is not a safety control. The eval caught the assistant answering a medication-dosage question with a target range and supplement suggestions, in the teacher's own voice, despite a prompt forbidding exactly that. Now medication, dosage, and crisis language route to fixed responses before any model runs. The crisis path breaks character, states plainly that this is an AI and not the teacher, and gives the 988 Suicide and Crisis Lifeline. Its first version matched 'end my life' but not 'ending my life'; the gap was found by testing, then closed.
Brand voice enforced in code, not just prompted. The client's style guide forbids em dashes. The model emitted three in a single answer despite an explicit instruction, so the pipeline now strips them deterministically. Same principle as the safety gate: if it has to be true, a prompt is not enough.
Rate limiting, and a verified vendor claim. Cloudflare's own rate-limiting binding deployed cleanly, reported '15 requests per 60 seconds', then allowed 20 consecutive requests with an identical key. It was a no-op on this account. It was replaced with a dozen lines of SQL, verified at 15 allowed and 5 blocked.
A security audit that found something, and what we did about it. An audit of the live index found that a single folder of personal notes, which should never have been ingested, had made private material retrievable. It was contained within the hour with a two-layer block, the source was excluded, 154,540 vectors were purged, and five regression tests now exist purely to prove it cannot come back. That purge is the biggest single reason the index is smaller today. A team that audits its own work and fixes what it finds is worth more than one that never looks.

The eval suite, last run

25 / 25
retrieval cases
13 / 13
answer cases
4 / 4
safety-gate cases
5 / 5
data-leak guards

These run on a schedule and log to a database on every commit, so a regression turns a row red the moment it happens, in front of us rather than in front of a student.

The full stack, named

Everything under the hood

Ingestion & data
Pythoncontent-sniffing extractor.pages / PDF / RTF / Word / TXTOtter.ai + WEBVTT parsingspeaker attributionoverlap chunkingdedicated story / quote extractorsSQLite manifest (incremental)retry / backoffidempotent re-ingest
Retrieval, on Cloudflare
Vectorize (namespace-partitioned)metadata index by content typeWorkers AI, embeddingsWorkers AI, rerankingcustom TypeScript Worker/search + /ingest endpointsconstant-time secret comparebatched writes (upsert/delete limits)
Retrieval quality
1024-dimensional embeddingstwo-stage: vector recall then rerankper-namespace isolationcontent-type filteringparallel story / quote channel
Orchestration, two lanes
n8nClaude Sonnet 5 agent (Lane A)gpt-oss 120B fixed pipeline (Lane B)LangChain.js (n8n AI nodes)agent loopforced think stepsearch Worker called as a toolSupabase Postgres memory, both lanesmulti-turn context
Front end & deploy
Squarespace embedpassword-protected pageinherits the site's typography and colourCORS restricted to client domainsSQL rate-limited proxylocal git repo, no remote
Safety, evaluation & guardrails
38-case evaluation harnessresults logged per git commitdeterministic pre-model safety gatemedication and crisis routing (988)deterministic em-dash strippingcontent-sniff exclusion of sensitive datanamespace isolationauthenticated endpointsconstant-time secret compare5 data-leak regression tests
On frameworks, for the technical reader. LangChain runs the agent loop, because n8n's AI nodes are built on LangChain.js. The retrieval layer is deliberately not: the Worker calls Cloudflare's APIs directly, with no vector-store abstraction, and the ingestion pipeline uses no document-loader framework. Going direct is what made namespace isolation, content-type filtering, and an explicit rerank stage straightforward to build and tune. The cost is owning the chunking, retry logic, and batch limits, which is exactly where the real bugs showed up during ingestion.

Want an assistant grounded in your own material?

If your business is sitting on years of documents, transcripts, or know-how that nobody can search, this is the pattern. Start with a free 20-minute call and we will talk through what it would take for your material.

Reach us directly
Call or text 607-280-2010
Email cgrant@gorges.us
Back to case studies →