How it works
Jev decides. Code computes and renders.
The model does one job: it makes fuzzy judgments about intent and hands back typed answers. Everything with consequences, from thresholds to words to payments, is deterministic code and content your team controls.
The system
Six steps from search data to the page
- 1Search Console exportQueries and landing pages, imported as a CSV.Code decides
- 2Page intent profilesEach query classified, then averaged by landing page. A historical prior.Jev decides
- 3Visitor sessionPage, source, scroll, time, pages viewed. Normalized, nothing identifying.Code decides
- 4Jev decisionLikely intent, readiness, help signal, confidence.Jev decides
- 5Policy + approved contentThresholds, guardrails and hysteresis pick an approved variant.Code decides
- 6Adaptive UIDeterministic components. Donation, forms and analytics.Code decides
Tinted steps are Jev’s. Everything else is ordinary code you can read and test.
The boundary
Who decides what
| Decision | Decided by | How |
|---|---|---|
| What a search query is probably after | Jev | Intent, topic and support readiness for each query in an import. Typed answers from a closed set, with confidence. |
| What a visitor is probably after right now | Jev | From a normalized snapshot of the session, or from a sentence the visitor typed. Fuzzy classification only. |
| How ready to give, and whether they need help | Jev | Readiness (with how sure it is), a help signal, a “can't give” signal and a “mostly researching” signal. |
| Whether a typed request asks for more than one thing | Jev | Scores how strongly the sentence asks for each path, independently. Code decides what to do with the scores. |
| Offering both paths when two are asked for | Code | Two paths at or above the threshold, from a typed request only, are shown as two tabs. Never inferred from behavior. |
| Which event a request is about, and whether they want tickets, a table or a sponsorship | Jev | Picks an id from the approved calendar (or a kind of event), and one of four actions. It never sees or writes an event. |
| Dates, times, prices, seat counts, totals and calendar files | Code | Computed from the approved event. Time zones, daylight saving and the .ics file are plain code, never a model. |
| Averaging queries into a page profile | Code | Share of clicks by intent per landing page, scaled down when a page has little data. |
| Whether help overrides everything | Code | A fixed rule with a low threshold. Any fundraising ask is removed, whatever else the model said. |
| Left alone, subtle or strong | Code | Confidence thresholds. Below the floor, nothing changes. |
| Whether the donation UI expands | Code | Needs readiness and confidence in that readiness. High readiness with low confidence changes nothing. |
| Offering two next steps | Code | When two intents are both plausible and close together. |
| Avoiding flicker | Code | A challenger must win repeatedly before the page changes. Help never has to wait. |
| The words on the page | Your team | Every headline, body and button is an approved variant. Jev picks a key. It never writes copy. |
| Dollar amounts and frequency | Code | Parsed from what the visitor typed by ordinary code. The model is never asked to extract or calculate. |
| Rendering, forms, payment, analytics | Code | Deterministic components. The model doesn't build HTML or touch a payment. |
| Experiment allocation and statistics | Code | A random on-device id and a standard two-proportion test. |
| Whether any of this runs | Your team | A kill switch on the overview and ?adaptive=off. Independent of any model. |
What Jev returns
Typed answers, never prose
- Intent: one of learn, take action, subscribe, volunteer, donate, receive help, or unknown, with a probability for each.
- Donation readiness: a 0–1 score, plus how confident the model is in that score.
- What a typed request asks for: a 0–1 score per path, so a sentence can ask for two things at once.
- Signals: researching, action-seeking, seeking help, can’t give. Each a 0–1 value.
- Topic and fitting next step: closed lists. The next-step answer is only used as a consistency check.
Every answer is a value from a fixed list, or a number between 0 and 1. There is nowhere for a model to put a dollar amount or a sentence.
What Jev never does
Kept out of the model on purpose
- Extract dollar amounts or gift frequency
- Calculate anything, or count
- Build markup or choose components
- Write, edit or personalize copy
- Decide anything about payment or eligibility
- Infer who someone is, or claim to know their search
A model answer is a signal, not a fact. When it is unsure, the page stays as it is.
Swapping the model
One interface, any classifier
The application depends on a single interface, not on any vendor. Jev, a different hosted model, a fine-tuned classifier or a plain rules engine can sit behind it, as long as it returns the same typed answers.
export interface DecisionProvider {
readonly name: string;
decide(input: DecisionInput, signal?: AbortSignal): Promise<VisitorDecision>;
classifyQuery(query: string, signal?: AbortSignal): Promise<QueryClassification>;
}- Implement
decideandclassifyQueryin a new provider, returning the shared types. - Return it from
getServerProvider()inlib/jev/server.ts, with the offline provider as the fallback. - Run the same assertions the offline classifier passes against your provider, then the policy tests. Policy doesn’t change.
Online and offline
With no key set, a local classifier with identical outputs answers, so the prototype runs anywhere. Set TYPESAFE_API_KEY on the server to use hosted Jev; the key never reaches the browser. ADAPTIVE_GIVING_FORCE_OFFLINE=true forces the local classifier even when a key is present. If a hosted call fails, that request falls back to the local classifier so the page never breaks.