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

  1. 1Search Console exportQueries and landing pages, imported as a CSV.Code decides
  2. 2Page intent profilesEach query classified, then averaged by landing page. A historical prior.Jev decides
  3. 3Visitor sessionPage, source, scroll, time, pages viewed. Normalized, nothing identifying.Code decides
  4. 4Jev decisionLikely intent, readiness, help signal, confidence.Jev decides
  5. 5Policy + approved contentThresholds, guardrails and hysteresis pick an approved variant.Code decides
  6. 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

Which decisions belong to Jev, to code, and to your team
DecisionDecided byHow
What a search query is probably afterJevIntent, 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 nowJevFrom a normalized snapshot of the session, or from a sentence the visitor typed. Fuzzy classification only.
How ready to give, and whether they need helpJevReadiness (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 thingJevScores how strongly the sentence asks for each path, independently. Code decides what to do with the scores.
Offering both paths when two are asked forCodeTwo 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 sponsorshipJevPicks 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 filesCodeComputed from the approved event. Time zones, daylight saving and the .ics file are plain code, never a model.
Averaging queries into a page profileCodeShare of clicks by intent per landing page, scaled down when a page has little data.
Whether help overrides everythingCodeA fixed rule with a low threshold. Any fundraising ask is removed, whatever else the model said.
Left alone, subtle or strongCodeConfidence thresholds. Below the floor, nothing changes.
Whether the donation UI expandsCodeNeeds readiness and confidence in that readiness. High readiness with low confidence changes nothing.
Offering two next stepsCodeWhen two intents are both plausible and close together.
Avoiding flickerCodeA challenger must win repeatedly before the page changes. Help never has to wait.
The words on the pageYour teamEvery headline, body and button is an approved variant. Jev picks a key. It never writes copy.
Dollar amounts and frequencyCodeParsed from what the visitor typed by ordinary code. The model is never asked to extract or calculate.
Rendering, forms, payment, analyticsCodeDeterministic components. The model doesn't build HTML or touch a payment.
Experiment allocation and statisticsCodeA random on-device id and a standard two-proportion test.
Whether any of this runsYour teamA 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>;
}
  1. Implement decide and classifyQuery in a new provider, returning the shared types.
  2. Return it from getServerProvider() in lib/jev/server.ts, with the offline provider as the fallback.
  3. 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.