lmjtfy.git / apps / lmjtfy / src / lib.rs
lib.rsannotatedlib.rssource1060 lines · 47.6 KB · raw

lmjtfy, the Worker: the page, the stream that answers a question, and the gate that runs as the visitor types.

POST /ask answers with Datastar events. Each one is the whole transcript as it stands, so the browser holds no state: it morphs what it is sent.

What happens to an input is decided by rules (rules::RULES). First Jev is asked, in one request, for every fact the rules want: can it judge the input, what kind of question is it, is it several, and its answer if it is one yes-or-no question. That alone ends most queries: a refusal, or a direct answer. Only a question that needs options or a scale written, or splitting up, goes to the LLM (Workers AI), and then Jev answers what it wrote, again in one request. Jev costs a hundredth of what the LLM does.

No call is sent from here. Each goes to the archive (archive.rs), which returns the response it already holds for that exact request, or makes the call inside the day's budget and keeps it.

19use std::convert::Infallible;
20use std::time::Duration;
22use ::archive::{Ask, Called, Pot};
23use ::card::Card;
24use ask::{Judged, Kept, Outcome, Prepared, Wanted};
25use axum::Router;
26use axum::body::Body;
27use axum::extract::{Extension, Json, Path, Query, State};
28use axum::http::{HeaderMap, Response, header};
29use axum::routing::{get, post};
30use datastar::prelude::{ElementPatchMode, PatchElements, PatchSignals};
31use futures_channel::mpsc::{UnboundedSender, unbounded};
32use futures_util::StreamExt;
33use jev_client::Client;
34use jev_protocol::ModelId;
35use jev_worker::{FetchTransport, WorkerRuntime};
36use llm::Model;
37use rules::{Effect, End, Fact, Network, Next, Note, Value, Want};
38use serde::Deserialize;
39use tower_service::Service;
40use worker::{Context, Env, HttpRequest, event};
41
42mod ai;
43pub mod archive;
44mod browse;
45pub mod clone;
46mod diagram;
47pub mod events;
48mod host;
49mod meatproxy;
50pub mod meter;
51mod object;
52mod past;
53mod playground;
54mod release;
55pub mod view;
56
57use view::{Ending, JevCall, LlmCall, LlmOutcome, Tool, View};
58
59const DATASTAR: &str = include_str!("../../../ds-bundle/datastar.js");

The pixel emoji the site draws (the online list's flags, the vote buttons): SerenityOS's, cut down to those (third-party/serenity-emoji).

62const EMOJI: &[u8] = include_bytes!("../../../third-party/serenity-emoji/emoji.woff2");

The commit this Worker was built from (build.rs). Pages carry it, and the archive tells every page that connects what it is now.

66pub const BUILD: &str = env!("LMJTFY_BUILD");

What this deploy changed, for the people on the site: the built commit's Release-Note: trailer, or empty (build.rs).

69pub const NOTE: &str = env!("LMJTFY_NOTE");

The secret's name: lmjtfy's own Jev key, apart from the estate's general one, as jevstrudel has its own.

73const KEY: &str = "LMJTFY_TYPESAFE_API_KEY";
74const JEV_MODEL: &str = "JEV_MODEL";
75const LLM_MODEL: &str = "LLM_MODEL";
76const JEV_DOLLARS_PER_DAY: &str = "JEV_DOLLARS_PER_DAY";

What one visitor may do in a minute: full answers, and the facts request that runs as they type. Counted by the budget object (meter::admit). Cloudflare's own rate limiting binding was tried first and never refused a request on the live site (2026-10-02: 45 asks in 40 seconds, limit 10).

81const ASKS_PER_MINUTE: u32 = 10;
82const GLANCES_PER_MINUTE: u32 = 60;
84#[derive(Clone)]
85struct App {
86    env: Env,
87}
88
89#[event(fetch)]
90async fn fetch(request: HttpRequest, env: Env, context: Context) -> worker::Result<release::Served> {
91    // A release file is answered here, before the router, and as the runtime's
92    // own response, so that its length survives (`release::Served`).
93    let host = request.headers().get(header::HOST).and_then(|host| host.to_str().ok()).unwrap_or_default();
94    if host::Host::of(host).serves_downloads()
95        && matches!(*request.method(), axum::http::Method::GET | axum::http::Method::HEAD)
96        && let Some(route) = release::route(request.uri().path()).filter(release::Route::is_file)
97    {
98        let event = events::of(&request);
99        return Ok(release::file(&env, event, route, request.method(), request.headers()).await);
100    }
101    route_request(request, env, context).await.map(release::Served::Http)
102}
103
104async fn route_request(mut request: HttpRequest, env: Env, context: Context) -> worker::Result<Response<Body>> {
105    // Each host has its own routes (`host.rs`): what an address does not
106    // serve is not there to be reached, rather than refused one by one.
107    let host = request.headers().get(header::HOST).and_then(|host| host.to_str().ok()).unwrap_or_default().to_owned();
108    let which = host::Host::of(&host);
109    let redirect = git_redirect(&env);
110    let state = App { env: env.clone() };
111    let routes = match which {
112        host::Host::Code => code_routes(),
113        // The site itself. With the redirect on, its repositories are at the
114        // code host and the gate below sends every path under one there; a
115        // dev server or staging, which have no code host, and the site
116        // before the redirect is switched on, serve them where they are.
117        host::Host::Home | host::Host::Elsewhere | host::Host::Other => site_routes(which.serves_git(redirect)),
118    };
119    // Release files are the code host's alone (and a dev server's or
120    // staging's, which have no code host): never the site's addresses.
121    let mut router = if which.serves_downloads() { with_downloads(routes) } else { routes }.with_state(state);
122    // The request as an event, before it is known what came of it
123    // (events.rs). A GET's is kept here, once it has a status, after the
124    // response has gone; a POST's by its handler, which knows how it ended.
125    let headers = request.headers().clone();
126    let event = events::of(&request);
127    let target = request.uri().path_and_query().map_or("/", |target| target.as_str());
128    let asked = host::Asked {
129        host: &host,
130        method: request.method(),
131        target,
132        socket: headers.contains_key(header::UPGRADE),
133        own: headers.get("sec-fetch-mode").is_some_and(|mode| mode != "navigate"),
134    };
135    match host::gate(&asked, redirect) {
136        host::Gate::Pass => {}
137        host::Gate::Moved { to, status } => {
138            let mut event = event.named("moved").with(&host);
139            event.status = f64::from(status.as_u16());
140            context.wait_until(async move { events::record(&env, event).await });
141            return Ok(Response::builder()
142                .status(status)
143                .header(header::LOCATION, to)
144                .header(header::CACHE_CONTROL, "public, max-age=86400")
145                .body(Body::empty())
146                .expect("static headers are valid"));
147        }
148        host::Gate::Refused(why) => {
149            let mut response = clone::refused(axum::http::StatusCode::METHOD_NOT_ALLOWED, &why);
150            response.headers_mut().insert(header::ALLOW, axum::http::HeaderValue::from_static("GET, HEAD"));
151            return Ok(response);
152        }
153    }
154    let got = event.clone().got();
155    request.extensions_mut().insert(event);
156    let mut response = router.call(request).await?;
157    if let Some(mut event) = got {
158        event.status = f64::from(response.status().as_u16());
159        // A page given to a browser is a visit, and says when it was counted.
160        let page = response.status().is_success() && response.headers().get(header::CONTENT_TYPE).is_some_and(|kind| kind.as_bytes().starts_with(b"text/html"));
161        if page
162            && let Some(cookie) = event.visit(&headers, js_sys::Date::now()).and_then(|cookie| axum::http::HeaderValue::from_str(&cookie).ok())
163        {
164            response.headers_mut().append(header::SET_COOKIE, cookie);
165        }
166        // Any page a browser is first given gives it its id, before it can
167        // ask anything, and that page's own event is the id's first: until
168        // 2026-10-03 only the home page gave one, and never said which.
169        if page
170            && event.browser.is_empty()
171            && event.client != "bot"
172            && let Some((id, cookie)) = new_browser()
173            && let Ok(cookie) = axum::http::HeaderValue::from_str(&cookie)
174        {
175            response.headers_mut().append(header::SET_COOKIE, cookie);
176            event.browser = id;
177        }
178        context.wait_until(async move { events::record(&env, event).await });
179    }
180    Ok(response)
181}

What lmjtfy.fun serves. With git, the repositories too, as a dev server and staging do (host::Host::Other).

185fn site_routes(git: bool) -> Router<App> {
186    let router = Router::new()
187        .route("/", get(page))
188        .route("/datastar.js", get(datastar))
189        .route("/emoji.woff2", get(emoji))
190        .route("/live.js", get(live_js))
191        .route("/icons/{name}", get(icon))
192        .route("/card.png", get(card))
193        .route(meatproxy::CARD_PATH, get(meatproxy_card))
194        .route("/robots.txt", get(robots))
195        .route("/rules", get(rules_page))
196        .route("/rules.svg", get(rules_svg))
197        .route("/ask", post(ask))
198        .route("/gate", post(gate))
199        .route("/rate", post(rate))
200        .route("/comment", post(comment))
201        .route("/seen", post(seen))
202        .route("/feed", get(feed))
203        .route("/live", get(live));
204    if git { with_repositories(router) } else { router }
205}

What code.lmjtfy.fun serves: the repositories, a front page listing them, and the little the code pages draw themselves with. No /ask, no archive, no /live, and no /seen: the pages there are told not to open sockets (Frame::live).

211fn code_routes() -> Router<App> {
212    with_repositories(
213        Router::new()
214            .route("/", get(clone::index))
215            .route("/emoji.woff2", get(emoji))
216            .route("/icons/{name}", get(icon))
217            .route("/card.png", get(code_card))
218            .route("/robots.txt", get(robots)),
219    )
220}

A repository's landing page and the pages under it, and the two requests git makes.

224fn with_repositories(router: Router<App>) -> Router<App> {
225    router
226        .route("/{repo}", get(clone::landing))
227        .route("/{repo}/", get(clone::landing))
228        .route("/{repo}/{*path}", get(browse::path))
229        .route("/{repo}/info/refs", get(clone::refs))
230        .route("/{repo}/git-upload-pack", post(clone::upload_pack))
231}

A repository's release files (release.rs): /<name>/latest/<kind>, /<name>/releases/<tag>/<asset> and the two pages. Everything under those two prefixes is the handler's, so what is not a route is its 404.

236fn with_downloads(router: Router<App>) -> Router<App> {
237    router
238        .route("/{repo}/latest", get(release::serve))
239        .route("/{repo}/latest/", get(release::serve))
240        .route("/{repo}/latest/{*rest}", get(release::serve))
241        .route("/{repo}/releases", get(release::serve))
242        .route("/{repo}/releases/", get(release::serve))
243        .route("/{repo}/releases/{*rest}", get(release::serve))
244}
246#[derive(Deserialize)]
247struct Asked {
248    #[serde(default)]
249    q: String,

Which version of each call the page wants (archive::Pins).

251    #[serde(default)]
252    pins: String,
253}

What /card.png is of: a question, or with code, the code.

256#[derive(Deserialize)]
257struct Pictured {
258    #[serde(default)]
259    q: String,
260    code: Option<String>,

With code, which repository: lmjtfy.git when absent.

262    repo: Option<String>,
263}

#[worker::send]: asking the budget object is not Send, and an axum handler's future must be.

267#[worker::send]
268async fn page(State(app): State<App>, headers: HeaderMap, Query(asked): Query<Asked>) -> Response<Body> {
269    let budget = shared(&app.env).await;
270    let home = archive::home(&app.env).await;
271    let typed = ask::clean(&asked.q);
272    // For the link preview: what Jev said, if this was asked before.
273    let said = if typed.is_empty() { None } else { archive::answer(&app.env, &typed).await };
274    let declined = said.is_none() && !typed.is_empty() && archive::was_declined(&app.env, &typed).await;
275    let page = view::page(&typed, said.as_ref(), declined, &origin(&headers), &host_of(&headers).code_origin(&origin(&headers), git_redirect(&app.env)), budget, home.as_ref());
276    respond("text/html; charset=utf-8", "no-cache", page.into_string().into())
277}

Whether the old addresses send the code to the code host (GIT_REDIRECT, host::GitRedirect). A value that is neither on nor off is logged as an error, on every request until it is fixed, and runs as off: the behaviour that cannot break a clone.

283fn git_redirect(env: &Env) -> host::GitRedirect {
284    let value = env.var(host::GitRedirect::VAR).ok().map(|var| var.to_string());
285    let (switch, complaint) = host::GitRedirect::resolve(value.as_deref());
286    if let Some(complaint) = complaint {
287        worker::console_error!("{complaint}");
288    }
289    switch
290}

Which of the site's addresses the request came to (host.rs).

293fn host_of(headers: &HeaderMap) -> host::Host {
294    host::Host::of(headers.get(header::HOST).and_then(|value| value.to_str().ok()).unwrap_or_default())
295}

Where this site is, for the preview's image address, which has to be whole. From the request, so the dev server previews itself.

299fn origin(headers: &HeaderMap) -> String {
300    let host = headers.get(header::HOST).and_then(|value| value.to_str().ok()).unwrap_or("localhost");
301    let scheme = if host.starts_with("localhost") || host.starts_with("127.") { "http" } else { "https" };
302    format!("{scheme}://{host}")
303}

The picture a link unfurls with: the question and what Jev said, read from the archive. It asks nothing, so a preview bot spends nothing.

307#[worker::send]
308async fn card(State(app): State<App>, headers: HeaderMap, Query(asked): Query<Pictured>) -> Response<Body> {
309    // `?code=<commit>`: the code page's picture. The commit is only there to
310    // change the address; what is drawn is the latest one read.
311    if asked.code.is_some() {
312        let repo = asked.repo.as_deref().and_then(clone::Repo::named).unwrap_or(clone::Repo::Lmjtfy);
313        let latest = clone::latest(&app.env, repo).await;
314        let url = format!("{}/{}", origin(&headers), repo.served());
315        let meta = clone::meta(&app.env, repo).await;
316        let about = meta.as_ref().map(|meta| meta.about()).unwrap_or_default();
317        let card = Card::Code { name: repo.served(), url: &url, recurse: !repo.submodules().is_empty(), about, branch: repo.branch(), latest: latest.as_ref() };
318        return respond("image/png", "public, max-age=3600", ::card::png(card).into());
319    }
320    let typed = ask::clean(&asked.q);
321    let said = if typed.is_empty() { None } else { archive::answer(&app.env, &typed).await };
322    let declined = !typed.is_empty() && said.is_none() && archive::was_declined(&app.env, &typed).await;
323    let said = said.map(|entry| entry.answers).unwrap_or_default();
324    let card = if typed.is_empty() {
325        Card::Home
326    } else if declined {
327        Card::Declined { input: &typed }
328    } else {
329        Card::Question { input: &typed, said: &said }
330    };
331    respond("image/png", "public, max-age=3600", ::card::png(card).into())
332}

/card.png at the code host: the code's picture only. A question's reads the archive, which that host does not.

336#[worker::send]
337async fn code_card(state: State<App>, headers: HeaderMap, Query(asked): Query<Pictured>) -> Response<Body> {
338    if asked.code.is_none() {
339        return clone::refused(axum::http::StatusCode::NOT_FOUND, "No such picture.");
340    }
341    card(state, headers, Query(asked)).await
342}

/live: a page's socket, handed to the archive, which keeps it and tells it how many pages are open and what happens while it is.

346#[worker::send]
347async fn live(State(app): State<App>, mut request: axum::extract::Request) -> Response<Body> {
348    // Where Cloudflare placed the page, for the online list. The Worker sets
349    // these headers itself, over any the page sent, so a page cannot name its
350    // own place; the archive keeps them on the socket while it is open.
351    // Only a socket is handed on: the archive reads any other request as a
352    // message from this Worker, and a visitor's must never be one.
353    if !request.headers().get(header::UPGRADE).is_some_and(|upgrade| upgrade.as_bytes().eq_ignore_ascii_case(b"websocket")) {
354        return clone::refused(axum::http::StatusCode::UPGRADE_REQUIRED, "Not a socket.");
355    }
356    let place = request.extensions().get::<worker::Cf>().map(|cf| (cf.country(), cf.city()));
357    // Who connected, for the archive to keep (`events`).
358    let event = request.extensions().get::<events::Event>().and_then(|event| serde_json::to_string(event).ok());
359    let headers = request.headers_mut();
360    headers.remove(archive::COUNTRY);
361    headers.remove(archive::CITY);
362    headers.remove(archive::EVENT);
363    if let Some(event) = event.and_then(|event| axum::http::HeaderValue::from_str(&view::url_encoded(&event)).ok()) {
364        headers.insert(archive::EVENT, event);
365    }
366    headers.insert(archive::PLACED, axum::http::HeaderValue::from_static("1"));
367    if let Some((country, city)) = place {
368        for (name, value) in [(archive::COUNTRY, country), (archive::CITY, city)] {
369            if let Some(value) = value.and_then(|value| axum::http::HeaderValue::from_str(&view::url_encoded(&value)).ok()) {
370                headers.insert(name, value);
371            }
372        }
373    }
374    let request = match worker::Request::try_from(request) {
375        Ok(request) => request,
376        Err(_) => return clone::refused(axum::http::StatusCode::BAD_REQUEST, "Not a socket."),
377    };
378    match archive::live(&app.env, request).await {
379        Ok(response) => response.into(),
380        Err(_) => clone::refused(axum::http::StatusCode::BAD_GATEWAY, "The archive is unreachable."),
381    }
382}

What is left of the day's shared budgets, for the page.

385async fn shared(env: &Env) -> Option<view::Shared> {
386    let status = meter::status(env).await?;
387    let jev_per_day = env.var(JEV_DOLLARS_PER_DAY).ok()?.to_string().parse().ok()?;
388    Some(view::Shared { status, neurons_per_day: llm::FREE_NEURONS_PER_DAY, jev_dollars_per_day: jev_per_day })
389}

The rules as an SVG file, for the READMEs.

392async fn rules_svg() -> Response<Body> {
393    respond("image/svg+xml", "public, max-age=3600", diagram::standalone(&Network::lmjtfy()).into())
394}

What crawlers are asked to leave alone. /rules with facts set is one page per combination of facts, each linking to more: on 2026-10-03 one crawler asked for 43,440 of them in a day. The page itself is welcome. Cloudflare puts its own lines above these.

400const ROBOTS: &str = "User-agent: *\nDisallow: /rules?\n";
402async fn robots() -> Response<Body> {
403    respond("text/plain; charset=utf-8", "public, max-age=3600", ROBOTS.into())
404}
405
406async fn datastar() -> Response<Body> {
407    respond("text/javascript; charset=utf-8", "public, max-age=86400", DATASTAR.into())
408}

The SharedWorker that holds one /live socket for a browser's tabs. Its address carries the build, so a deploy's pages get a new one; the script itself does not change with it, so it may be kept.

413async fn live_js() -> Response<Body> {
414    respond("text/javascript; charset=utf-8", "public, max-age=3600", include_str!("live.js").into())
415}
417mod pictures {
418    include!(concat!(env!("OUT_DIR"), "/icons.rs"));
419}

/icons/<name>.png: one of the explorer's pixel icons (third-party/jerrys-pixel-icons), from the table build.rs writes. They do not change under a name, so a browser may keep one for good.

424async fn icon(Path(name): Path<String>) -> Response<Body> {
425    let found = name.strip_suffix(".png").and_then(|name| pictures::ICONS.binary_search_by_key(&name, |(icon, _)| *icon).ok());
426    match found {
427        Some(at) => respond("image/png", "public, max-age=31536000, immutable", Body::from(pictures::ICONS[at].1)),
428        None => clone::refused(axum::http::StatusCode::NOT_FOUND, "No such icon."),
429    }
430}

/meatproxy/card: the card image of the site the LLM suggestion links to, from here so the visitor's browser never asks that site for it. Nothing in the request is read.

435#[worker::send]
436async fn meatproxy_card() -> Response<Body> {
437    meatproxy::image().await
438}
440async fn emoji() -> Response<Body> {
441    respond("font/woff2", "public, max-age=604800", Body::from(EMOJI))
442}

The rules with the facts the link sets. Nothing is asked of anyone.

445async fn rules_page(Query(params): Query<Vec<(String, String)>>) -> Response<Body> {
446    let network = Network::lmjtfy();
447    let play = playground::play(&network, &params);
448    let says = playground::says(&network, &play);
449    let page = view::playground(&network, &play.known, &play.fired, &says, |clicked| playground::link(&play.known, clicked));
450    respond("text/html; charset=utf-8", "no-cache", page.into_string().into())
451}

Who is asking, for the visitor limit: Cloudflare's own header for the visitor's address, a key in the budget object's memory for a minute.

455fn visitor(headers: &HeaderMap) -> String {
456    headers.get("cf-connecting-ip").and_then(|value| value.to_str().ok()).unwrap_or("unknown").to_owned()
457}

Datastar posts the page's signals as JSON; q is the search box.

460async fn ask(State(app): State<App>, Extension(event): Extension<events::Event>, headers: HeaderMap, Json(asked): Json<Asked>) -> Response<Body> {
461    let visitor = visitor(&headers);
462    let browser = browser(&headers);
463    let pins = ::archive::Pins::parse(&asked.pins);
464    stream(move |events| answer(app, asked.q, pins, visitor, browser, event.named("answer"), events))
465}

A vote on Jev's answer: the search box still holds the question, and the button sets which way.

469#[derive(Deserialize)]
470struct Rated {
471    #[serde(default)]
472    q: String,
473    vote: String,
474}

How many votes a visitor may cast a minute.

477const VOTES_PER_MINUTE: u32 = 30;

/rate: a browser's vote on Jev's answer, and the votes after it, as the rating element. A browser with no id cannot vote; a question never answered has nothing to vote on.

482#[worker::send]
483async fn rate(State(app): State<App>, Extension(event): Extension<events::Event>, headers: HeaderMap, Json(rated): Json<Rated>) -> Response<Body> {
484    let input = ask::clean(&rated.q);
485    let browser = browser(&headers);
486    let vote = match rated.vote.as_str() {
487        "up" => Some(::archive::Vote::Up),
488        "down" => Some(::archive::Vote::Down),
489        _ => None,
490    };
491    let rating = match (&browser, vote) {
492        (Some(browser), Some(vote)) if !input.is_empty() && meter::admit(&app.env, "rate", visitor(&headers), VOTES_PER_MINUTE).await => {
493            archive::rate(&app.env, &input, browser, vote).await
494        }
495        _ => archive::rating(&app.env, &input, browser.as_deref().map(|browser| asker(browser, &input))).await,
496    };
497    events::record(&app.env, event.named("vote").with(rated.vote.chars().take(8).collect::<String>()).about(&input)).await;
498    let patch = PatchElements::new(view::rating(rating.as_ref(), browser.is_some()).into_string()).into_datastar_event();
499    // The comment box is open while there is a vote to comment on. A vote
500    // changed leaves what was typed, and it may be saved again for the new vote.
501    let open = PatchSignals::new(serde_json::json!({ "open": rating.is_some_and(|rating| rating.mine.is_some()), "saved": false }).to_string()).into_datastar_event();
502    respond("text/event-stream", "no-cache", Body::from(format!("{patch}{open}")))
503}

A comment on the vote: the page's signals, q the search box and comment the textarea.

507#[derive(Deserialize)]
508struct Commented {
509    #[serde(default)]
510    q: String,
511    #[serde(default)]
512    comment: String,
513}

How many comments a visitor may save a minute: fewer than votes, since each one is free text kept for the owner to read.

517const COMMENTS_PER_MINUTE: u32 = 5;

/comment: more words on the browser's standing vote, kept beside it (archive::comment). Answered with the page's notes: thanks, or why not (fixed words), and never the text. What the visitor typed is only ever handed to the archive: it is in no log line and no event, which keep how it ended (saved, empty, ...) and nothing else.

524#[worker::send]
525async fn comment(State(app): State<App>, Extension(event): Extension<events::Event>, headers: HeaderMap, Json(said): Json<Commented>) -> Response<Body> {
526    use ::archive::Unsaid;
527    let input = ask::clean(&said.q);
528    let browser = browser(&headers);
529    let outcome = match (&browser, ::archive::clean_comment(&said.comment)) {
530        _ if !meter::admit(&app.env, "comment", visitor(&headers), COMMENTS_PER_MINUTE).await => Err(Unsaid::Limit),
531        (_, Err(unsaid)) => Err(unsaid),
532        (None, _) => Err(Unsaid::NoVote),
533        (_, Ok(_)) if input.is_empty() => Err(Unsaid::NoVote),
534        (Some(browser), Ok(clean)) => archive::comment(&app.env, &input, browser, clean).await,
535    };
536    let kept = match outcome {
537        Ok(()) => "saved",
538        Err(Unsaid::Empty) => "empty",
539        Err(Unsaid::TooLong) => "too-long",
540        Err(Unsaid::NoVote) => "no-vote",
541        Err(Unsaid::Limit) => "limit",
542        Err(Unsaid::Failed) => "failed",
543    };
544    events::record(&app.env, event.named("comment").with(kept).about(&input)).await;
545    let note = PatchElements::new(view::comment_error(outcome.err()).into_string()).into_datastar_event();
546    let saved = PatchSignals::new(serde_json::json!({ "saved": outcome.is_ok() }).to_string()).into_datastar_event();
547    respond("text/event-stream", "no-cache", Body::from(format!("{note}{saved}")))
548}

How many reports a visitor's pages may make a minute.

551const REPORTS_PER_MINUTE: u32 = 120;

/seen: a page's report of itself as it is left, or as a link off the site is followed (page.js): how long it was in view, how far down, the screen. Kept as a read or out event; the page is told nothing.

556#[worker::send]
557async fn seen(State(app): State<App>, Extension(event): Extension<events::Event>, headers: HeaderMap, body: String) -> Response<Body> {
558    if let Ok(report) = serde_json::from_str::<::archive::Report>(&body)
559        && meter::admit(&app.env, "seen", visitor(&headers), REPORTS_PER_MINUTE).await
560    {
561        events::record(&app.env, event.seen(&report)).await;
562    }
563    Response::builder().status(axum::http::StatusCode::NO_CONTENT).body(Body::empty()).expect("no headers to be wrong")
564}

The cookie that tells one browser from another, for counting each once per question. A random id the Worker gives a browser on its first page, kept a year. askers and ratings get only asker of it; events keeps it as it is, which is what ties one browser's rows together.

570const BROWSER: &str = "lmjtfy_browser";

This browser's id, if it has one.

573fn browser(headers: &HeaderMap) -> Option<String> {
574    let cookies = headers.get(header::COOKIE)?.to_str().ok()?;
575    cookies
576        .split(';')
577        .filter_map(|pair| pair.trim().split_once('='))
578        .find(|(name, _)| *name == BROWSER)
579        .map(|(_, id)| id.to_owned())
580        .filter(|id| id.len() == 36 && id.bytes().all(|b| b.is_ascii_hexdigit() || b == b'-'))
581}

A new browser id, as a Set-Cookie value, for a browser that has none.

584fn new_browser() -> Option<(String, String)> {
585    use wasm_bindgen::JsCast;
586    let crypto = js_sys::Reflect::get(&js_sys::global(), &"crypto".into()).ok()?;
587    let uuid = js_sys::Reflect::get(&crypto, &"randomUUID".into()).ok()?.dyn_into::<js_sys::Function>().ok()?.call0(&crypto).ok()?.as_string()?;
588    let cookie = format!("{BROWSER}={uuid}; Max-Age=31536000; Path=/; HttpOnly; Secure; SameSite=Lax");
589    Some((uuid, cookie))
590}

The browser for one question: its id and the question, hashed together, so the archive can tell a browser asking again from a new one and can link nothing else.

595pub(crate) fn asker(browser: &str, input: &str) -> ::archive::Asker {
596    use sha2::{Digest, Sha256};
597    let digest = Sha256::new().chain_update(browser.as_bytes()).chain_update([0]).chain_update(input.as_bytes()).finalize();
598    ::archive::Asker(digest.iter().map(|byte| format!("{byte:02x}")).collect())
599}

Where "asked lately" was scrolled to: the last line shown.

602#[derive(Deserialize)]
603struct Scrolled {
604    ms: f64,
605    q: String,
606}

/feed: the page of "asked lately" after the last line shown, put before the feed's end, and a new end. It reads the archive and asks nothing.

610#[worker::send]
611async fn feed(State(app): State<App>, Query(scrolled): Query<Scrolled>) -> Response<Body> {
612    let after = ::archive::Cursor { asked_ms: scrolled.ms, input: scrolled.q };
613    // An archive that cannot be read ends the feed here, rather than asking
614    // again for as long as the end is in view.
615    let entries = archive::older(&app.env, after).await.unwrap_or_default();
616    let (lines, end) = view::older(&entries);
617    let end = PatchElements::new(end.into_string()).selector("#more").mode(ElementPatchMode::Replace);
618    let mut body = String::new();
619    if !entries.is_empty() {
620        let lines = PatchElements::new(lines.into_string()).selector("#more").mode(ElementPatchMode::Before);
621        body.push_str(&lines.into_datastar_event().to_string());
622    }
623    body.push_str(&end.into_datastar_event().to_string());
624    respond("text/event-stream", "no-cache", Body::from(body))
625}

The same signals, while the visitor is still typing: the facts alone.

628async fn gate(State(app): State<App>, Extension(event): Extension<events::Event>, headers: HeaderMap, Json(asked): Json<Asked>) -> Response<Body> {
629    let visitor = visitor(&headers);
630    stream(move |events| glance(app, asked.q, visitor, event.named("gate"), events))
631}

An event stream fed by work. Jev and Workers AI calls are not Send (they hold JS values) and an axum handler must be, so the work runs on the isolate's own executor and reaches the response through a channel. The open response body is what keeps the request alive while it runs.

637fn stream<W, F>(work: W) -> Response<Body>
638where
639    W: FnOnce(UnboundedSender<String>) -> F,
640    F: Future<Output = ()> + 'static,
641{
642    let (events, stream) = unbounded::<String>();
643    wasm_bindgen_futures::spawn_local(work(events));
644    respond("text/event-stream", "no-cache", Body::from_stream(stream.map(Ok::<_, Infallible>)))
645}
647fn respond(content_type: &'static str, cache: &'static str, body: Body) -> Response<Body> {
648    Response::builder()
649        .header(header::CONTENT_TYPE, content_type)
650        .header(header::CACHE_CONTROL, cache)
651        .body(body)
652        .expect("static headers are valid")
653}

Sends markup to morph into the page. A closed channel means the visitor left; there is nobody to tell.

657fn patch(events: &UnboundedSender<String>, markup: maud::Markup) {
658    let event = PatchElements::new(markup.into_string()).into_datastar_event();
659    let _ = events.unbounded_send(event.to_string());
660}
662fn show(events: &UnboundedSender<String>, view: &View) {
663    patch(events, view::transcript(view));
664}

Everything a Jev call needs, built once per request.

667pub(crate) struct Jev {
668    pub(crate) client: Client<FetchTransport, WorkerRuntime>,
669    pub(crate) model: ModelId,
670    pub(crate) dollars_per_day: f64,
671}

Why no Jev call can be made at all.

674pub(crate) enum NoJev {

The Worker has no API key.

676    Offline,
677    Broken(String),
678}
680impl From<NoJev> for Ending {
681    fn from(none: NoJev) -> Self {
682        match none {
683            NoJev::Offline => Ending::Offline,
684            NoJev::Broken(error) => Ending::Failed { error },
685        }
686    }
687}

The Jev client. The Worker builds one to prepare requests and to know the site is online; the archive builds one to send them.

691pub(crate) fn jev(env: &Env) -> Result<Jev, NoJev> {
692    let key = env.secret(KEY).map(|secret| secret.to_string()).unwrap_or_default();
693    if key.trim().is_empty() {
694        return Err(NoJev::Offline);
695    }
696    let var = |name: &str| env.var(name).map(|var| var.to_string()).map_err(|e| NoJev::Broken(format!("{name}: {e}")));
697    let model = ModelId::pinned(&var(JEV_MODEL)?).map_err(|e| NoJev::Broken(e.to_string()))?;
698    let dollars_per_day: f64 = var(JEV_DOLLARS_PER_DAY)?
699        .parse()
700        .map_err(|_| NoJev::Broken(format!("{JEV_DOLLARS_PER_DAY} is not a number")))?;
701    let client = Client::new(FetchTransport::api(), WorkerRuntime, model.clone(), key.trim())
702        .map_err(|e| NoJev::Broken(e.to_string()))?;
703    Ok(Jev { client, model, dollars_per_day })
704}
706fn spent(pot: Pot) -> Ending {
707    Ending::Spent {
708        what: match pot {
709            Pot::Jev => "Jev",
710            Pot::Llm => "the LLM",
711        },
712    }
713}

Jev's answer to a prepared call, from the archive: the response it holds for this exact request, or the one it gets now. Err means the call was never made, so the page must not show it.

Which kept response, or whether to send again, is what the page's pins say for this request (archive::Pins::pick); the version it got comes back beside the answer.

722async fn judged_by_jev(
723    env: &Env,
724    jev: &Jev,
725    input: &str,
726    wanted: Wanted,
727    prepared: &Prepared,
728    pins: &::archive::Pins,
729) -> Result<(Outcome, Option<view::Versions>), Ending> {
730    let id = ::archive::call_id(jev_protocol::ENDPOINT, &prepared.request);
731    let ask = Ask::Jev { input: input.to_owned(), wanted, request: prepared.request.clone(), pick: pins.pick(&id) };
732    match archive::call(env, ask).await {
733        Ok(Called::Answered(record)) => Ok((
734            ask::read(
735            &jev.model,
736            prepared,
737            Kept {
738                body: &record.response,
739                sent: record.sent(),
740                took: Duration::from_secs_f64(record.took_ms / 1000.0),
741                attempts: record.attempts,
742                request_id: record.request_id,
743            },
744            ),
745            Some(view::Versions { id, version: record.version, versions: record.versions }),
746        )),
747        Ok(Called::Failed { error, request_id, took_ms }) => {
748            Ok((Outcome::Failed { error, request_id, took: Duration::from_secs_f64(took_ms / 1000.0), dollars: 0.0 }, None))
749        }
750        Ok(Called::Spent(pot)) => Err(spent(pot)),
751        Ok(Called::NotKept) => Err(Ending::NotKept { id }),
752        Err(error) => Err(Ending::Failed { error }),
753    }
754}

Asks Jev for facts about the input, all in one request, and records what they turned out to be. This is the rules' backfill: every Jev fact any live rule is waiting on, together.

759async fn learn(
760    env: &Env,
761    jev: &Jev,
762    view: &mut View,
763    facts: Vec<Fact>,
764    events: Option<&UnboundedSender<String>>,
765) -> Result<(), Ending> {
766    let failed = |error: String| Ending::Failed { error };
767    let wanted = Wanted::Facts(facts.clone());
768    let prepared = ask::wanted(&jev.model, &view.input, &wanted).map_err(|e| failed(e.to_string()))?;
769    view.facts = Some(JevCall::pending(&prepared));
770    if let Some(events) = events {
771        show(events, view);
772    }
773    let outcome = match judged_by_jev(env, jev, &view.input, wanted, &prepared, &view.pins).await {
774        Ok((outcome, kept)) => {
775            view.facts.as_mut().expect("just set").kept = kept;
776            outcome
777        }
778        Err(ending) => {
779            view.facts = None;
780            return Err(ending);
781        }
782    };
783    // Several facts can be read from one answer: each finds its question's.
784    let learned = match &outcome {
785        Outcome::Answered { judged, .. } => facts
786            .iter()
787            .map(|fact| {
788                let id = ask::question_id(*fact);
789                prepared
790                    .parts
791                    .iter()
792                    .position(|part| part.id == id)
793                    .and_then(|index| judged.get(index))
794                    .and_then(|judged| ask::learned(*fact, judged))
795                    .map(|value| (*fact, value))
796                    .ok_or_else(|| failed(format!("Jev's answer for {} is not the type that was asked", fact.name())))
797            })
798            .collect::<Result<Vec<_>, _>>(),
799        Outcome::Failed { error, .. } => Err(failed(error.clone())),
800    };
801    view.facts.as_mut().expect("just set").outcome = Some(outcome);
802    for (fact, value) in learned? {
803        view.known.learn(fact, value);
804    }
805    Ok(())
806}

Jev's probability that it can judge the input, once the facts are in.

809fn answerable(view: &View) -> Option<f64> {
810    match view.facts.as_ref()?.judged(ask::question_id(Fact::Answerable))? {
811        Judged::Noul(p_yes) => Some(*p_yes),
812        _ => None,
813    }
814}
816async fn answer(
817    app: App,
818    input: String,
819    pins: ::archive::Pins,
820    visitor: String,
821    browser: Option<String>,
822    mut event: events::Event,
823    events: UnboundedSender<String>,
824) {
825    let began = js_sys::Date::now();
826    let mut view = View::new(ask::clean(&input));
827    let browsing = pins.browsing();
828    view.pins = pins;
829    // Every ask counts towards the visitor's limit, an empty one too.
830    view.ending = Some(if !meter::admit(&app.env, "ask", visitor, ASKS_PER_MINUTE).await {
831        Ending::Slow
832    } else if view.input.is_empty() {
833        Ending::Empty
834    } else {
835        decide(&app.env, &mut view, &events).await
836    });
837    // The card of the suggestion's site, for the endings that make one.
838    if matches!(view.ending, Some(Ending::NotAQuestion { .. } | Ending::NoQuestion)) {
839        view.preview = meatproxy::head().await;
840        // So a link to this question can say what it is (`Card::Declined`).
841        archive::declined(&app.env, &view.input).await;
842    }
843    show(&events, &view);
844    // What was asked, how it ended and what it took. An old version looked
845    // at is an ask all the same, and says so.
846    let (sent, kept) = view.calls();
847    event.detail = if browsing { format!("{} (browsing)", events::ending(view.ending.as_ref())) } else { events::ending(view.ending.as_ref()).to_owned() };
848    (event.sent, event.kept) = (f64::from(sent), f64::from(kept));
849    event.llm = if view.llm.is_some() { 1.0 } else { 0.0 };
850    event.took_ms = js_sys::Date::now() - began;
851    events::record(&app.env, event.about(&view.input)).await;
852    // The page keeps the versions it is looking at, and no others.
853    let pinned = PatchSignals::new(serde_json::json!({ "pins": view.pinned(), "comment": "", "open": false, "saved": false }).to_string()).into_datastar_event();
854    let _ = events.unbounded_send(pinned.to_string());
855    // The budget and the feed are the home page's, and it is hidden while an
856    // answer is up (page.css), so neither is sent again here. An old version
857    // looked at is not what Jev says now, so it is not kept as the answer.
858    if matches!(view.ending, Some(Ending::NotAQuestion { .. } | Ending::NoQuestion)) {
859        // Declined was kept before the stream was shown, so there is a
860        // record to vote on (`archive::DECLINED`).
861        let who = browser.as_deref().map(|browser| asker(browser, &view.input));
862        let rating = archive::rating(&app.env, &view.input, who).await;
863        patch(&events, view::rating(rating.as_ref(), browser.is_some()));
864        let open = PatchSignals::new(serde_json::json!({ "open": rating.is_some_and(|rating| rating.mine.is_some()) }).to_string()).into_datastar_event();
865        let _ = events.unbounded_send(open.to_string());
866    }
867    if matches!(view.ending, Some(Ending::Answered)) && !browsing {
868        // The rules say whether it may be shown: Jev judged it fit.
869        let listed = Network::lmjtfy().notes(&view.known, Note::List);
870        let who = browser.as_deref().map(|browser| asker(browser, &view.input));
871        archive::asked(&app.env, &view.input, view.said(), view.llm.is_some(), listed, browser.as_deref()).await;
872        // The votes on the answer as it is now kept.
873        let rating = archive::rating(&app.env, &view.input, who).await;
874        patch(&events, view::rating(rating.as_ref(), browser.is_some()));
875        // A vote already cast on this answer has its box open.
876        let open = PatchSignals::new(serde_json::json!({ "open": rating.is_some_and(|rating| rating.mine.is_some()) }).to_string()).into_datastar_event();
877        let _ = events.unbounded_send(open.to_string());
878    }
879}

Does what the rules say until they say the query has ended. The rules (rules::RULES) decide the order; this only carries each step out and records what it taught.

884async fn decide(env: &Env, view: &mut View, events: &UnboundedSender<String>) -> Ending {
885    let jev = match jev(env) {
886        Ok(jev) => jev,
887        Err(none) => return none.into(),
888    };
889    let network = Network::lmjtfy();
890    loop {
891        let (by, next) = network.decide(&view.known);
892        view.fired.extend(by);
893        let step = match next {
894            Next::Ask(facts) => learn(env, &jev, view, facts, Some(events)).await,
895            // One call writes everything the drafting rules that hold want.
896            Next::Do(Effect::Draft(_)) => drafted(env, view, &network.wants(&view.known), events).await,
897            Next::Do(Effect::Judge) => judge(env, &jev, view, events).await,
898            Next::End(End::NotAQuestion) => {
899                return match answerable(view) {
900                    Some(p_yes) => Ending::NotAQuestion { p_yes },
901                    None => Ending::Failed { error: "the rules refused an input Jev was not asked about".into() },
902                };
903            }
904            // Nothing is left to do. What to show is whatever the rules noted.
905            Next::Done if view.answered() => return Ending::Answered,
906            Next::Done => return Ending::NoQuestion,
907        };
908        if let Err(ending) = step {
909            return ending;
910        }
911        show(events, view);
912    }
913}

Asks Jev every question the LLM wrote, in one request.

916async fn judge(env: &Env, jev: &Jev, view: &mut View, events: &UnboundedSender<String>) -> Result<(), Ending> {
917    let drafts: Vec<(String, llm::Draft)> =
918        view.tools.iter().filter_map(|tool| Some((tool.id.clone(), tool.draft()?.clone()))).collect();
919    let wanted = Wanted::Drafted(drafts);
920    let prepared =
921        ask::wanted(&jev.model, &view.input, &wanted).map_err(|e| Ending::Failed { error: e.to_string() })?;
922    view.judging = Some(JevCall::pending(&prepared));
923    show(events, view);
924    match judged_by_jev(env, jev, &view.input, wanted, &prepared, &view.pins).await {
925        Ok((outcome, kept)) => {
926            let judging = view.judging.as_mut().expect("just set");
927            judging.outcome = Some(outcome);
928            judging.kept = kept;
929        }
930        Err(ending) => {
931            view.judging = None;
932            return Err(ending);
933        }
934    }
935    view.known.learn(Fact::Judged, Value::Bool(view.any_judged()));
936    Ok(())
937}

The LLM's questions for the input, from the archive, as the tool calls it made. A call Jev's protocol would refuse is kept, marked, and not sent.

941async fn drafted(env: &Env, view: &mut View, wants: &[Want], events: &UnboundedSender<String>) -> Result<(), Ending> {
942    let failed = |error: String| Ending::Failed { error };
943    let id = env.var(LLM_MODEL).map(|var| var.to_string()).map_err(|e| failed(format!("{LLM_MODEL}: {e}")))?;
944    let model = Model::find(&id).ok_or_else(|| failed(format!("{id} is not one of llm's candidates")))?;
945    let request = llm::request(&view.input, wants);
946    view.llm = Some(LlmCall { model: model.id, request: request.clone(), outcome: None, kept: None });
947    show(events, view);
948
949    let id = ::archive::call_id(model.id, &request);
950    let pick = view.pins.pick(&id);
951    let record = match archive::call(env, Ask::Llm { model: model.id.to_owned(), request, pick }).await {
952        Ok(Called::Answered(record)) => record,
953        Ok(Called::Failed { error, took_ms, .. }) => {
954            view.llm.as_mut().expect("just set").outcome = Some(LlmOutcome {
955                body: error.clone(),
956                neurons: 0.0,
957                took: Duration::from_secs_f64(took_ms / 1000.0),
958                dropped: 0,
959                sent: ask::Sent::Now,
960            });
961            return Err(failed(format!("the LLM: {error}")));
962        }
963        Ok(Called::Spent(pot)) => {
964            view.llm = None;
965            return Err(spent(pot));
966        }
967        Ok(Called::NotKept) => {
968            view.llm = None;
969            return Err(Ending::NotKept { id });
970        }
971        Err(error) => {
972            view.llm = None;
973            return Err(failed(error));
974        }
975    };
976    let parsed = llm::parse(&record.response);
977    view.llm.as_mut().expect("just set").kept = Some(view::Versions { id, version: record.version, versions: record.versions });
978    view.llm.as_mut().expect("just set").outcome = Some(LlmOutcome {
979        neurons: parsed.as_ref().map_or(0.0, |reply| model.neurons(reply.usage)),
980        took: Duration::from_secs_f64(record.took_ms / 1000.0),
981        dropped: parsed.as_ref().map_or(0, |reply| reply.dropped),
982        sent: record.sent(),
983        body: record.response,
984    });
985    let reply = parsed.map_err(|error| failed(format!("the LLM: {error}")))?;
986    view.tools = reply
987        .calls
988        .into_iter()
989        .enumerate()
990        .map(|(index, call)| {
991            let refused = call.draft.as_ref().ok().and_then(|draft| {
992                if !llm::takes(wants, draft) {
993                    // Jev has answered that reading itself, or the rules did not take it.
994                    return Some(format!("a {} was not what the rules asked the LLM for", draft.tool()));
995                }
996                ask::check(draft).err().map(|e| e.to_string())
997            });
998            // q1, q2, ...: the question's id in its request and on the page.
999            Tool { id: format!("q{}", index + 1), call, refused }
1000        })
1001        .collect();
1002    let any = view.tools.iter().any(|tool| tool.draft().is_some());
1003    view.known.learn(Fact::Drafted, Value::Bool(any));
1004    Ok(())
1005}

The facts alone, for the line under the search box. It is the same request /ask begins with, so what was typed is already kept by the time it is asked.

1010async fn glance(app: App, input: String, visitor: String, event: events::Event, events: UnboundedSender<String>) {
1011    let mut view = View::new(ask::clean(&input));
1012    let admitted = !view.input.is_empty() && meter::admit(&app.env, "glance", visitor, GLANCES_PER_MINUTE).await;
1013    // What the feed has that this could be the start of, first: it asks
1014    // nobody anything, so it is there before Jev has answered. With
1015    // nothing typed, or nothing found, the list is emptied.
1016    let suggested = if admitted { archive::suggest(&app.env, &view.input).await } else { Vec::new() };
1017    patch(&events, view::suggested(&suggested));
1018    let seen = if !admitted {
1019        None
1020    } else {
1021        match (jev(&app.env), Network::lmjtfy().next(&view.known)) {
1022            (Ok(jev), Next::Ask(facts)) => learn(&app.env, &jev, &mut view, facts, None).await.ok(),
1023            _ => None,
1024        }
1025    };
1026    let (sent, kept) = view.calls();
1027    let mut event = event.with(if seen.is_some() { "seen" } else { "" });
1028    (event.sent, event.kept) = (f64::from(sent), f64::from(kept));
1029    events::record(&app.env, event.about(&view.input)).await;
1030    let glance = seen.and_then(|()| answerable(&view)).map(|answerable| view::Glance { answerable, kind: view.reading(), unfinished: Network::lmjtfy().notes(&view.known, Note::Unfinished) });
1031    patch(&events, view::live(glance));
1032}
1034#[cfg(test)]
1035mod tests {
1036    use super::*;
1037
1038    #[test]
1039    fn a_browser_is_the_same_asker_of_one_question_and_unrelated_across_two() {
1040        let id = "0b5f2a1e-7c3d-4e8f-9a6b-1c2d3e4f5a6b";
1041        assert_eq!(asker(id, "is it?"), asker(id, "is it?"));
1042        assert_ne!(asker(id, "is it?"), asker(id, "is it not?"));
1043        assert_ne!(asker(id, "is it?"), asker("1b5f2a1e-7c3d-4e8f-9a6b-1c2d3e4f5a6b", "is it?"));
1044        assert_eq!(asker(id, "is it?").0.len(), 64);
1045        assert!(!asker(id, "is it?").0.contains(id));
1046    }
1047
1048    #[test]
1049    fn only_a_well_formed_browser_cookie_is_read() {
1050        let with = |cookie: &str| {
1051            let mut headers = HeaderMap::new();
1052            headers.insert(header::COOKIE, cookie.parse().unwrap());
1053            browser(&headers)
1054        };
1055        let id = "0b5f2a1e-7c3d-4e8f-9a6b-1c2d3e4f5a6b";
1056        assert_eq!(with(&format!("a=b; {BROWSER}={id}")).as_deref(), Some(id));
1057        assert_eq!(with(&format!("{BROWSER}=<script>")), None);
1058        assert_eq!(with("a=b"), None);
1059    }
1060}