Chapter 3: the source, read in the order it runs

A dozen or so files. They are easiest read in the order a question meets them: in through the router, out to the archive, back as HTML. You do not need to read Rust fluently to follow along. Every file opens with a comment (//!) that says what it is for, and those are worth reading first.

flowchart TD
  L["lib.rs: routes, and the loop that answers"] --> AR["archive.rs: the archive object"]
  L --> M["meter.rs: the budget object"]
  AR --> AI["ai.rs: Workers AI"]
  AR --> O["object.rs: talking to an object"]
  M --> O
  L --> V["view.rs: the page as HTML"]
  V --> D["diagram.rs: the rules as SVG"]
  V --> VC["view/: code pages, toasts"]
  L --> P["playground.rs: /rules"]
  L --> C["clone.rs: git, and the code page"]
  C --> B["browse.rs: folders and files"]

1. lib.rs: the front door

The routes are at the top (fetch): one line per address, each sent to a function. The interesting one is ask, which starts the loop that answers a question. It does not decide anything itself. It asks the rules engine (Network::next, chapter 6) what to do, does that, records what it learned, and asks again, until the rules say it is done. Each turn, the whole transcript so far is sent to the browser as one Datastar event.

Aside. Why re-send the whole transcript every time instead of just the new bit? Because then the browser has no state to get wrong. It throws away what it had and shows what it was sent. A reconnect, a missed event, a second tab: none of them can leave the page in a state the server does not know about.

The calls themselves are made by learn (the facts request), drafted (the LLM) and judge (Jev judging what the LLM wrote), and every one of them goes through the archive. glance is the as-you-type request behind /gate. answer records what was asked so it can join the feed.

2. archive.rs: the memory, and the only door out

The archive is a Durable Object, and it is the only code that sends a request to Jev or the LLM. Ask it for a call and it either hands back the response it kept from last time, or makes the call once, keeps it, and hands that back. Two visitors asking the same new thing at once share one call (once). Chapter 9 is about why.

It also keeps the feed of questions (asked), the tallies (counts), and the /live sockets of every open page, and it pushes the toasts.

3. meter.rs: the budget object

The other Durable Object. Before a call goes out, the archive asks it to hold the call's worst-case cost; after, to settle it at the real cost. It also reads the whole Cloudflare account's AI usage, so a dev server or the eval spending the same free allocation is counted too, and it keeps each visitor's per-minute count, in memory only. Chapter 10 has the arithmetic.

4. object.rs and ai.rs: the plumbing

object.rs posts a JSON message to a Durable Object and reads one back. ai.rs calls Workers AI through the AI binding, with JSON text in and JSON text out, so the reply can be shown exactly as it came.

5. view.rs and view/: everything you see

view.rs turns a View (what has happened so far) into HTML with maud. It is pure: no network, no clock, so its tests run on your laptop. The answer stamps, the bars, the panels that show every request and response pretty-printed, the home page's feeds: all here. Chapter 4 covers view/.

diagram.rs draws the rules engine as an SVG, marked with what is known about the current question: what held, what failed, which rules fired and in what order.

6. playground.rs: /rules

The rules with facts you set by clicking: every test in the diagram is a link that sets its fact. It runs the same engine and asks nobody anything.

7. clone.rs and browse.rs: the code, shared

clone.rs is a tiny git server, or rather a forwarder: it passes a clone's two requests to GitHub with a read-only token and refuses everything else. The same address in a browser is the code's front page. browse.rs reads folders and files from GitHub's API so they can be shown as pages, like this one.

8. page.css and page.js

The look, copied on purpose from typesafe.ai: near-black on white, one pink band, dithered dot fields, old-computer windows. And the only JavaScript the site has: typing a ?q= question in, the copy buttons, the /live socket (online count, toasts, live feeds, the reload offer), and enlarging a diagram on a click, and telling /seen how long a page was in view and how far down it was read as it is put away. live.js is the SharedWorker that holds that socket once for all of a browser's tabs.

9. events.rs: what happened, kept

As the Worker serves a request it makes an event of it: what it was, what came of it, and everything the request and Cloudflare said about where it came from. The archive keeps each one as a row. Nothing on the site shows them; the owner reads them from a separate admin Worker. Chapter 2 says what is in a row, and chapter 9 has the event itself.

Aside. Keeping the row must not slow the page. So the Worker answers first and writes after: wait_until lets a Worker finish a job once the response has left.

For the people who maintain it

FileWhat
lib.rsThe routes, and the loop that does what the rules say next, streamed as the transcript.
archive.rsThe Archive Durable Object: every answered request keyed by the exact body, the calls themselves, the feed, the tallies, the /live sockets.
meter.rsThe Budget Durable Object: hold and settle, the account's usage, per-visitor counts.
object.rsPosting a JSON message to a Durable Object.
ai.rsThe Workers AI binding, JSON text in and out.
view.rsThe page and the transcript as HTML. A View in, markup out.
view/The code pages, the toasts, and view.rs's tests.
diagram.rsThe rules as an SVG of the Rete network, and /rules.svg.
playground.rs/rules: facts set by the link.
clone.rsgit clone: smart HTTP forwarded read-only; the code page; the latest commit; clone and pull counting.
browse.rs/lmjtfy.git/<path>: folders and files from GitHub's contents API, kept a minute per isolate; jevcrates at its pin.
page.css, page.jsThe look, and the browser code.
events.rsA request as an archive::Event, with Cloudflare's account of where it came from, and the sending of it to the archive.
live.js/live.js: the SharedWorker that holds one /live socket for every tab of a browser.

The invariants for all of these (what must never await, what must stay pure, where calls may be made) are in the Worker's CLAUDE.md, one folder up.

← Previous: Chapter 2, the Worker · Up: apps/lmjtfy · Next: Chapter 4, view/ →