lmjtfy.git / README.md
README.mdpreviewREADME.mdsource212 lines · 12.0 KB · raw
1# lmjtfy: a guide in fifteen chapters
2
3**Let Me Jev That For You.** You know the joke: someone asks a question they
4could have searched for, so you send them a link that slowly types it into a
5search box for them. This is that joke, aimed at Jev, and then it gets
6carried away and actually answers.
7
8Jev is TypeSafe AI's "System One" model. It does not write. You cannot ask
9it to explain anything. You hand it a question whose answers are already
10written down (yes or no; one of these five; somewhere on this scale), and it
11tells you how likely each one is, with numbers you can trust. That is the
12whole trick, and this site is built around the one thing Jev cannot do,
13which is write the question.
14
15It is live at <https://lmjtfy.fun>. Jev's own documentation
16is at <https://docs.typesafe.ai>.
17
18> **Try it.** Open
19> <https://lmjtfy.fun/?q=Is+a+slot+machine+a+good+retirement+plan%3F>
20> and watch. The question types itself, Jev answers, and under the answer is
21> every call that was made to get it, with the bytes that crossed the wire.
22> (That question has been asked before, so nothing is spent: you get the
23> kept answer. Chapter 9 explains why that matters so much.)
24
25## How to read this
26
27Every folder in this repository is a chapter, and each one ends with a link
28to the next. Read them in order and you will know how all of it works; jump
29in anywhere and the chapter tells you what it is and what is beside it.
30Each chapter starts with the idea, in plain words, and ends with the parts a
31maintainer needs (files, invariants, commands). The `CLAUDE.md` tab on each
32folder is what an AI agent working here is told on top of the chapter.
33
34| Chapter | Folder | What you learn |
35| --- | --- | --- |
36| 1 | [apps/](apps/) | Why there is one app, and what an "app" is here. |
37| 2 | [apps/lmjtfy/](apps/lmjtfy/) | The Worker: everything it serves, keeps and pushes. |
38| 3 | [apps/lmjtfy/src/](apps/lmjtfy/src/) | The Worker's files, in the order to read them. |
39| 4 | [apps/lmjtfy/src/view/](apps/lmjtfy/src/view/) | Pages as pure functions: data in, HTML out. |
40| 5 | [packages/](packages/) | The libraries that do no I/O, which is most of the thinking. |
41| 6 | [packages/rules/](packages/rules/) | A rules engine, a Rete network, and why it batches. |
42| 7 | [packages/ask/](packages/ask/) | The nine questions Jev is asked about every question. |
43| 8 | [packages/llm/](packages/llm/) | The LLM, kept on a short leash. |
44| 9 | [packages/archive/](packages/archive/) | Never send the same request twice. |
45| 10 | [packages/budget/](packages/budget/) | Two daily budgets, and being fair to strangers. |
46| 11 | [packages/card/](packages/card/) | Drawing a PNG one pixel at a time, in under ten milliseconds. |
47| 12 | [packages/tree/](packages/tree/) | How these very pages are made. |
48| 13 | [tools/](tools/) | The helpers that are not the site. |
49| 14 | [tools/eval/](tools/eval/) | Choosing an LLM with a test, not a hunch. |
50| 15 | [ds-bundle/](ds-bundle/) and [third-party/](third-party/) | What came from elsewhere. |
51
52## If you know Nuxt
53
54It is a website, and its parts have counterparts in any JavaScript
55framework. Here they are next to Nuxt's.
56
57| Here | What it is | In Nuxt terms |
58| --- | --- | --- |
59| A Cloudflare Worker | The whole server, run at Cloudflare's edge on each request. | The Nitro server, deployed to Cloudflare. |
60| Rust, compiled to WebAssembly | The language of all of it; `wasm-bindgen` lets it run inside the Worker's JavaScript runtime. | TypeScript, compiled ahead of time. |
61| axum | Routes a request to a handler (`apps/lmjtfy/src/lib.rs`). | `server/api/*.ts` and h3. |
62| maud | HTML written in Rust, checked by the compiler, escaped by default. | `.vue` templates. |
63| Datastar | The page sends what was typed; the server streams back HTML that replaces parts of the page. No client state, no JSON API. | Vue's reactivity, but the server owns the state. |
64| Durable Objects | One small server per name, with its own SQLite, that every request can reach: the archive and the budgets. | A database and a singleton service in one. |
65| `packages/*` | Pure Rust libraries with no I/O, tested with `cargo test`. | Composables and `utils/`, with unit tests. |
66| mise | Every tool at an exact version, and every task, from `mise.toml`. | `package.json` and a Node version manager. |
67
68## The whole story in one picture
69
70Here is what happens between Enter and the answer. Every arrow is a chapter
71somewhere in this guide.
72
73```mermaid
74sequenceDiagram
75  participant B as Browser
76  participant W as Worker (axum)
77  participant A as Archive (Durable Object)
78  participant J as Jev
79  participant L as LLM (Workers AI)
80  B->>W: POST /ask {q}
81  W->>A: facts about q?
82  A->>J: one request, nine questions
83  J-->>A: probabilities
84  A-->>W: kept, and shared with anyone else asking
85  W-->>B: SSE: the transcript so far
86  alt the rules need options or a scale written
87    W->>A: the LLM's tool call
88    A->>L: the request
89    L-->>A: tool calls
90    W->>A: Jev, judge them
91    A->>J: one request
92    J-->>A: answers
93  end
94  W-->>B: SSE: the answer, and every call that made it
95  A-->>B: every open page: a toast, and the feeds
96```
97
98In words:
99
1001. **Jev is asked about the question first**: nine typed questions in one
101   request. Can it be judged at all? What kind of question is it? Is it
102   several? And what is Jev's own answer, read each way? (Chapter 7.)
1032. **Rules decide what that means** (chapter 6). Most questions end right
104   here, with a refusal or with Jev's own answer, and no LLM is involved.
1053. **Only when something has to be written**, the options of a pick, the
106   levels of a scale, the parts of several questions, does the LLM write it,
107   as tool calls (chapter 8).
1084. **Jev judges what the LLM wrote**, in one more request.
109
110> **Aside.** Why go to Jev first, before the clever model that can write?
111> Price. A Jev request costs about a hundredth of an LLM call, so every
112> question Jev can settle by itself is a call the LLM never gets. The whole
113> design leans that way: ask the cheap honest thing first, and only call in
114> the expensive eloquent thing when someone has to write.
115
116## Running it yourself
117
118Anyone who clones it can build and test it. No GitHub account needed.
119
120<img src="https://mise.jdx.dev/logo.svg" alt="mise" height="22"> **Use [mise](https://mise.jdx.dev)**
121(`curl https://mise.run | sh` puts it in `~/.local/bin`: no root, no nix). It installs the rest of the
122toolchain at the versions in [mise.toml](mise.toml), and from then on you run only `mise` commands:
123
124    git clone --recurse-submodules https://code.lmjtfy.fun/lmjtfy.git
125    cd lmjtfy
126    mise trust && mise install    # Rust, wasm-bindgen, worker-build, node, wrangler, hk, pitchfork, fnox
127    mise tasks                    # everything that can be run
128    mise run check                # versions and the tests
129    mise run build                # the Worker, for WebAssembly, into apps/lmjtfy/build
130
131`mise run check` is what a clone needs to know the code is sound: its tests cover everything but the
132Worker's I/O and run natively. What must already be on the machine is a C compiler and git (and
133`mise run submodules` fetches the submodules if the clone forgot `--recurse-submodules`). The git
134hooks are `mise run hooks:install` (hk).
135
136> **With nix.** `nix develop` is the same thing with mise and the native libraries (a C compiler, OpenSSL)
137> provided for you, and it changes nothing about the commands above. The nix outputs here do not
138> need to be built to use the repository.
139
140## The other projects, and the client they share
141
142Three more of the owner's projects ask Jev, and all four share one client,
143jevcrates. Each is served at <https://code.lmjtfy.fun>, which lists them all with the command to clone each, with its own pages:
144
145| Clone | What |
146| --- | --- |
147| <https://code.lmjtfy.fun/jevcrates.git> | The Rust client for Jev. To use it in your own project, add it to `Cargo.toml` straight from this site: its front page has the lines. |
148| <https://code.lmjtfy.fun/postjevsql.git> | Jev in Postgres: a pgrx extension, in SQL. |
149| <https://code.lmjtfy.fun/jevsnes.git> | Jev plays A Link to the Past on a SNES core. |
150| <https://code.lmjtfy.fun/jevhooks.git> | Jev judges Claude Code's hooks. |
151
152To run the Worker itself you need your own Cloudflare account (Workers AI
153has no local emulation) and a TypeSafe API key. [fnox.toml](fnox.toml) declares the names the dev
154server reads (`CLOUDFLARE_API_TOKEN`, `CLOUDFLARE_ACCOUNT_ID`, `LMJTFY_TYPESAFE_API_KEY`,
155`LMJTFY_GITHUB_TOKEN`) and holds no value: export them, or give fnox a provider of your own in
156`fnox.local.toml`. Then `mise run dev` (which stops at once, saying why, if the Cloudflare
157credentials are missing) starts the Worker on <http://localhost:8787/> under
158pitchfork, and `mise daemons logs worker` and `mise daemons stop worker` read and stop it. The
159budgets and the clone proxy run without their secrets, counting only themselves and answering
160clones with 503.
161
162## For the owner
163
164The same tasks, with the owner's Cloudflare account. `mise run dev` supervises the dev server with
165[pitchfork](https://pitchfork.jdx.dev) (the `worker` daemon in `mise.toml`, as in `~/aifleet`): wrangler
166rebuilds the Worker when a source changes, and if it dies, pitchfork starts it again
167(`tools/wrangler-dev`). The owner's shell, `nix develop .#owner`, adds the tools that read this
168deployment's credentials from 1Password through the private `nix-facts` and `nix-pkgs` inputs
169(`lmjtfy-wrangler`, `lmjtfy-secret`, `lmjtfy-eval`; `op-env-run` is the host's). `tools/wrangler` uses
170`lmjtfy-wrangler` when it is on PATH, so in that shell nothing needs to be in the environment:
171
172    nix develop .#owner -c op-env-run -- mise run dev         # http://localhost:8787/
173    mise daemons logs worker                                  # what it is doing
174    mise daemons stop worker
175
176To deploy, staging first (each stops the dev server, since a deploy builds into its `build/`):
177
178    mise run deploy-staging                               # https://staging.lmjtfy.fun, behind the owner's login
179    mise run deploy                                       # the site and code.lmjtfy.fun (a custom domain in wrangler.toml), once staging is right; the toml says what is live: GIT_REDIRECT is on, so the old addresses send clones to the code host
180    mise run deploy -- --var GIT_REDIRECT:off             # roll back: clones are served at lmjtfy.fun again (cached 308s linger up to a day)
181    nix develop .#owner -c op-env-run -- lmjtfy-secret jev   # only when the Jev key changes (`… jev staging` for staging's)
182    mise run dev
183
184Without that shell, `deploy` and `deploy-staging` take `CLOUDFLARE_API_TOKEN` and `CLOUDFLARE_ACCOUNT_ID` from
185the environment (or fnox), as above.
186
187Staging is the same code as a Worker of its own, `lmjtfy-staging`, with its
188own archive and budgets: a migration runs there first, on Cloudflare's own
189engine, and a change is looked at there before a visitor sees it. It is not
190for the public, since every ask spends real money: Cloudflare Access admits
191only the owner and the agents' service token. Its secrets are its own and
192are set the same way, with `staging` as a second word.
193
194Both read the Cloudflare token from 1Password, which asks for approval on
195the Windows side: an "authorization timeout" means its prompt was not
196answered. The site is public and every visitor spends from the shared
197budgets. The secrets are listed in chapter 2,
198[apps/lmjtfy/](apps/lmjtfy/#credentials).
199
200## Files at the top
201
202| File | What |
203| --- | --- |
204| [Cargo.toml](Cargo.toml) | The Cargo workspace: every crate, and the versions they share. |
205| [mise.toml](mise.toml) | The toolchain with its versions, the tasks (`check`, `test`, `build`, `dev`, `deploy`), and the dev server's daemon. The one place a version lives. |
206| [hk.pkl](hk.pkl) | The git hooks, run by hk. |
207| [fnox.toml](fnox.toml) | The secret names the owner's tasks read, with no values. |
208| [flake.nix](flake.nix) | The nix shells, which only wrap mise: `nix develop` for anyone, `nix develop .#owner` for the owner's tools. |
209| [.gitmodules](.gitmodules) | The jevcrates submodule, by a relative URL (chapter 15). |
210| [.envrc](.envrc) | direnv: the owner's nix shell where it can be built, the plain one elsewhere. |
211
212Next: [Chapter 1, apps/](apps/) →