Design notes
Dated, long-form notes on what was built, measured or refused, and why. Kept as written.
- A database that remembers what changed — 2026-10-02
The change clock (a-resource-over-a-table) says that something was committed. This round adds what: m0-sqlite stamps each row a watched table writes, so "what changed since N" is a query. It lands as SPEC O26, O27 and…
- A Datastar form, end to end — shipped 2026-09-12
since the two-transports round — swap on a <form> emits data-on:submit__prevent="@post('url', {contentType: 'form'})" — and one unit test pinned that string. No browser had sent it. The todo demo used signals for…
- A fragment that names itself, 2026-09-10
Every one of the 172 rows docs/SPEC.md carried at 1.0.0 was about serving Python. The tree's own Mojo apps were seven server.mojo files that each routed by if path == or a handler-id chain, wrote every byte of HTML as a…
- A hold that replays — 2026-10-06
A design note from the engineering record. D65 is the decision; SPEC I33 names the capability. It retires the known issue "A WSGI hold replays nothing on reconnect", open since
- A login in the layer — 2026-09-27
A design note from the engineering record. SPEC N43–N45 are the rows, D53 the decision; D38 and D44
- A login on the notes app — shipped 2026-09-12
named the reason: wyhash64 is a hash, not a MAC, so nothing the framework could sign was worth signing. The primitive landed ahead of the login — m0_core.hmac, HMAC-SHA256 against the seven RFC 4231 vectors — because…
- A Mojo mount from somewhere else
--mount PREFIX=mojo serves a Mojo handler on pool threads that never touch Python. The handler is a compile-time type, so no command line can name one against a prebuilt binary: an application that wants its own Mojo…
- A resource over a table — 2026-10-02
Two applications on the layer's pattern registered the same routes over every table they served, and one experiment had measured how a rendering of a table is kept current. This round lifts the first into the table and…
- A signpost in a forked child
2026-09-21. test_two_processes_open_one_fresh_database has gone red on macOS CI twice in two days, both times on a pull request that touched nothing near it: the failure that prompted #364 (which taught the test to name…
- A slice keeps the GIL: the hand-off went back to the thread that had just held it — shipped 2026-09-26
The pre-release run of 2026-09-26 (Linux, 4 vCPUs) recorded it as "fairness probe max outlier on a VM; needs a run on the reference Mac": probe-pool-fairness's fair arm held its p99 and failed its max. It was not the…
- A stream has no deadline — 2026-09-30
A design note from the engineering record. D57 is the decision; SPEC A24 names
- A swap that moves the address bar
2026-09-21. The first application outside apps/ on the layer — unotes, a reader over a private notes corpus, the application-layer soak — has a filter form and a list of links, all of them swaps. Its log's third…
- A trait and a directory name, 2026-09-15
From 2026-08-28: an app cannot conform to a trait defined in a precompiled package. The evidence was two experiments. PoolHandler, defined in m0-http/src/mojo_pool.mojo, was moved into the source-resolved fork because…
- A vocabulary an application defines — 2026-09-18
Fragment[V: Vocabulary] takes the frontend library as a type parameter. DECISIONS D7 kept its two conformances, Htmx and Datastar, inside html.mojo, because on Mojo 1.0 an application's conformance to a trait behind a…
- A world the page cannot hold — wired 2026-09-16
up to sixteen blobs, steps them ten times a second, traces each into a 48-vertex polygon and publishes the whole state as one Datastar patch-signals frame. The page is sixteen full-stage divs whose clip-path is bound to…
- Scheduling stickiness: which worker wins the accept race is CPU placement, not load
eighty accepts in a row to the same worker — reproduced, and it is CPU placement, not load. Seen once (2026-08-29, ubuntu CI runner,
- Accept sharing: workers sharing a listener share its connections — shipped 2026-09-05
--workers N has every worker wait on the one listener the supervisor bound, and whichever worker wakes first drains the backlog until EAGAIN. On an idle box that is the same worker nearly every time, and a keep-alive…
- Agent usability runs — 2026-10-07
Fifteen headless coding-agent sessions on the two quickstarts' task and on a Django Channels baseline, scored from their transcripts. The harness is scripts/agent_runs.py; the briefs are scripts/agent_runs/. The…
- The ASGI executor per core, against uvicorn with uvloop — 2026-10-06
apps/asgi_bare under wrk -t2 -c16 with browser headers, the shape of the benchmark page's ASGI row, m0serve 1.11.0 at 2746288. Each run did three rounds, with the arms alternating within a round and uvicorn re-measured…
- CI and the one required check: why the workflows are shaped as they are — moved out of CLAUDE.md 2026-09-28
Several smokes compute a real quantity and print it — RSS growth over 10k requests, a fast request's latency behind two slow views, sendfile's RSS delta. A guard tells you pass or fail; it does not tell you the number…
- A conformance-suite tier
The server is pinned by hand-written probes against the RFC text -- smoke-ws speaks RFC 6455 from stdlib sockets, test_parsing.mojo covers the smuggling shapes directly -- and by no external suite on any cadence. A…
- MiniLM on the Neural Engine, served — measured 2026-09-04
apple-m1 build), uvicorn 0.52.4 with uvloop and --interface wsgi. Every row is one sequential run on an idle machine, driven one configuration at a time. The app and harness are bench/embed_coreml; the engine is…
- The desktop-Mac server, and what the wheel gives up to ship
position may be as a desktop server — a Mac mini or Studio running an application on hardware someone already owns — rather than as a Linux container competing with Granian and uvicorn on rps. The reasoning is that…
- The detached loop — shipped 2026-09-03
Under --blocking-threads and under the ASGI executor the event loop is an acceptor: it parses, parks a request, and writes the answer a Python thread produces. None of that needs Python. Yet DetachingBackend.wait…
- m0 dev, m0 image, and a release nobody has run — 2026-09-20
The third of three pull requests that let an application in Mojo be written outside this repository. The first added the wheel and the commands that need no template (the-m0-wheel); the second m0 new and its two…
- The Django server aims
Where the WSGI work is headed, and what gates each step — the full analysis with evidence is the design
- The drain does not read a request body in flight — resolved
Found by the soak driver's uploads population on color-separation (2026-09-02): with 9.7 MB multipart uploads in flight, every SIGTERM drain took exactly its 5 s budget, and the bisection put it on uploads alone.…
- The elastic pool: one thread until a job has waited — 2026-09-06
Bare WSGI at one worker, apps/wsgi_bare, wrk -t2 with browser headers, Apple M4, CPython 3.13.6, granian 2.8.2, the same binary with the rules on and off in the same session, arms alternated. Zero-config went from…
- The executor's per-request Python work, and the C-API head read — shipped 2026-09-04
With the loop thread off the GIL, the ASGI executor at 256 connections did 157–161k rps on 1.83 cores and at 16 connections 100–109k on 1.6 — ahead of a single uvicorn+uvloop process per core at saturation and behind it…
- The fairness probe judges order, on every pull request — shipped 2026-09-26
probe-pool-fairness was a pre-release probe. Its first run on Linux found a starvation the reference Mac had never shown (a-slice-keeps-the-gil), and a pre-release probe would catch a regression only when the next…
- v0.1.0: the first release
backpressure and Last-Event-ID replay, auth, CORS, config, health, JSON-lines access logging, graceful shutdown, multi-worker fork
- Flags and a doctor for the host — 2026-09-19
A Mojo host application was configured by environment alone. That was decided, not overlooked: D29 recorded "no CLI flags and no --doctor" with the retiring condition "a flag its operator cannot set in the environment",…
- Functions inside the query — 2026-09-30
m0-sqlite can now register a Mojo type as a scalar SQL function on a connection, and the function runs inside SQLite's query plan, reading each value where SQLite already holds it. SPEC O19 to O22 are its rows, D58 and…
- The GIL and the handler pool — measured 2026-09-09
--blocking-threads N puts N handler threads behind one event loop. SPEC C5 proves the isolation that buys: a slow view no longer holds the keep-alive connections pinned behind it. Users then assume the other half — that…
- Grant-verified holds — shipped 2026-09-11
proved the mechanism: a MojoPool thread takes an M0-Hold the way a WSGI pool thread does, and the loop drains it. It also found that the application this was for, textshelf, authorizes every stream before naming its…
- An SSE hold from a Mojo mount — shipped 2026-09-11
capabilities proven by demos and no real application. textshelf, which runs on this server in production, was the named candidate, and its SSE views the named path — and D18 stood in the way: MojoPool refused every…
- Hold on a pool thread: the refusal that keeps --realtime off real applications
(REAL_APP_VALIDATION.md, 2026-08-26) showed the realtime mode is not deployable for the application class it exists for, and the reason is a refusal this server makes on purpose: --realtime refuses --blocking-threads,…
- Inbound WebSocket flow control — shipped 2026-08-31
The outbound direction was credit-gated (websocket.send awaits its window) and the inbound direction had no backpressure of any kind. Once the executor's submit channel filled, WSGIHandler.ws_message discarded each…
- Where the loop inversion wins, measured on a constrained box — 2026-09-08
On the benchmark box the pump wins. It uses 1.6–1.8 cores to do it, and on a ten-core laptop those cores are free, so the row that reads "inverted is 1.16x the pump per core" also reads "the pump serves 1.6x the…
- libpq keeps its handle — 2026-10-01
m0-sqlite found on 2026-09-30 that its pin did not do on macOS what its row said (SPEC O23; functions-inside-the-query), and fixed it there. The pin was m0-postgres's code, copied, so m0-postgres had the same defect and…
- The loop inversion — in progress 2026-08-28
The handoff's item 1: run the Mojo loop's pass as a callback inside the executor's run_forever, on one thread, so a request goes parse → app → response with no datagram and no cross-thread wake. At c16 the pump batches…
- The one-thread WSGI gap is the loop thread, not the bridge — measured 2026-09-05
At one worker and one handler thread, m0serve serves bare WSGI at about 0.73–0.76x Granian's rate, and the layer-split table's per-core arithmetic put that deficit in the bridge, the per-request crossing into CPython.…
- The loop thread's user space — measured and cut, 2026-09-06
Bare WSGI at one worker and one handler thread, main at 043ac1d and this branch, the same session, arms alternated: 186k requests per second against 170k at 16 connections, 202k against 182k at 64, and 205k against 186k…
- Loops on threads — 2026-09-18
The Mojo host served one loop per process and refused M0_THREADS with 78, because that variable was m0serve's and a host that ignored it would serve a configuration other than the one written down. The reasons m0serve…
- m0serve and the runtime a fork cannot carry — 2026-09-25
The Mojo host refuses M0_WORKERS above 1 when its binary links MAX's parallel runtime, because a parallelize in a forked worker never returns (SPEC E32, DECISIONS D48, threads-first-for-m0-apps). That note ended with a…
- The Mojo handler pool — shipped 2026-08-28
The offload pool, for handlers written in Mojo. Planned as a kill-criterion spike: if a slow Mojo handler did not strand the connections behind it the way a slow Python view does, the branch was to be deleted and this…
- Mojo language capabilities, surveyed 2026-08-28
A pass over what the tree uses of the language, prompted by "are we fully tapping Mojo?". The short answer is yes wherever it was measured to pay — SoA span-based headers (+72%, the largest single win here), SIMD…
- A mut argument is not a reference — 2026-09-29
B20 (PR #463) lost two writes the same way. _flush_inverted and dispatch_job took the asyncio executor's ExecutorState as a mut argument, code re-entered during the call wrote to that struct through its address, and…
- One renderer, two transports, 2026-09-11
A fragment that names itself built the fragment layer for htmx and left one claim unproven: that the same renderer's output could go out as an HTTP body to htmx and as a datastar-patch-elements frame to every connected…
- The outbox sweep — taken, scoped (2026-08-29)
The second lever the inversion entry named. Every pass swept all 1,024 slots for a streaming one to drain, and the miss path — two flag loads per slot, none set — measured 1.2–1.3 µs per pass in isolation, on a pass…
- Periodic work off the event loop — measured 2026-09-14
connections against a trivial handler, driven from Python. The absolute throughput is the driver's, not the server's — every arm pays the same driver cost, so the comparisons hold and the rps figures do
- The pool handoff moved into memory — shipped 2026-09-05
Bare WSGI at one worker and one handler thread, the same binary with the ring on and off in the same session, is 155k requests per second against 131k at 16 connections and 184k against 154k at 256. The loop thread's…
- The pool's fast-route tail was the keep-alive cap — 2026-09-07
scripts/probes/bench_slow.py: Django, wrk -t2 -c16, a fresh server per arm and round, arms alternated, with N curl loops on /slow?ms=200 beside the fast route — the mixed-workload script's shape. Apple M4 (4 performance…
- Proven once, unloaded: an inventory of the gates with that shape
The v0.15.1 bug came from a shape rather than an oversight: proven once, by a smoke, and stressed not at all. The measure of how little that guarantees is exact. Reverting the websocket.send credit gate was caught on…
- Pacing the pump's loop thread
What the sweep measurement exposed: at c16 the pump's throughput depends on how long its loop thread spends per pass, in a way that an accidental 1.2 µs improved by 3%. An explicit pause before flushing a partial batch…
- Mojo 1.0's PythonObject leak, and what the pin bump will hit
per __setitem__ value. Upstream toolchain bug, measured directly (a dict passed to a no-op Python function 1000 times gains 1000 references). m0-wsgi works around it by never letting a per-request Python object cross…
- A request body still arriving at SIGTERM held the drain to its deadline — resolved
The drain loop read nothing new, so a half-received upload was neither completed nor closed until the 5 s budget expired: the client was reset and the process exited at 5.09 s. Found by the soak driver's uploads…
- Considered, not built: routes that carry a function
Router.match returns an Int and the caller dispatches on it, which is why three of five Mojo apps skip the router and hand-write if path == …. Route-to-function is reachable on Mojo 1.0 — verified by spike, not assumed.…
- Which language the executor shim should be in — decided 2026-09-05
About 1,500 lines of Python, exec'd into a fresh namespace per PyBridge (so per worker, per serving thread, per pool thread and per executor). It holds five
- m0-sqlite at run time — 2026-09-25
Until this round m0-sqlite reached SQLite through external_call, which put libsqlite3 on the link line of every binary that used it: no flag on macOS, where the dyld shared cache resolves it, and -Xlinker -lsqlite3 plus…
- Storage and MAX in the m0 wheel — 2026-09-25
The m0 wheel carried five source trees and no way to a database, and no word about MAX. This round adds both, on the two decisions the rounds before it made: m0-sqlite opens its library at run time (D49), so it can ride…
- Streamed WSGI bodies — shipped 2026-08-27
The last buffered shape. A generator the application did not size — Django's StreamingHttpResponse, the thing every Django SSE tutorial returns — was joined whole by the shim, so a never-ending one never answered and…
- Hardening the streaming seam — shipped 2026-08-27
0.14.0 shipped a fix for a hang that had been live all day: the shim keyed a connection slot's executor state — credit window, event, disconnect mark — by SLOT, while the loop recycles a slot the instant it closes a…
- Structured CI results
to $M0_RESULTS as one JSON line; the smoke job renders them into the run summary with a headroom column and uploads ci-results-<os>. Five sites are instrumented: the WSGI and ASGI RSS guards, the pool's fast-request…
- The accept batch: new connections wait for the ones already held — shipped 2026-09-26
The pre-release run of 2026-09-26 (Linux, 4 vCPUs) listed it as "the unbounded epoll accept drain; loop-only starvation". Both listeners are edge-triggered — kqueue EV_CLEAR, epoll EPOLLET — so one readiness event…
- The blobs kernel — landed 2026-09-16
Every blob adds strength / (d² + 1) to a 192 × 192 grid. Marching squares cuts the grid where the field crosses 1, and the cuts are chained into closed outlines. Each outline is resampled to 48 vertices and given the…
- The bus and its doors: fan-out across workers, Postgres NOTIFY and the names the server keeps — moved out of CLAUDE.md 2026-09-28
M0_WORKERS>1 forks, and each worker gets its own subscriber registry; a broadcast reaches other workers' subscribers only when everything shared is created before the fork (listener, BroadcastBus, SharedAtomics id slot)…
- Do the benchmark page's conclusions hold on Linux? Two of four do not — 2026-09-08
The three headline shapes, in the m0lin container (Linux aarch64, an 8-vCPU VM), against the same comparators at the same versions: granian 2.8.2, uvicorn with and without uvloop. Three rounds, arms alternated within…
- The demo in its own image — 2026-09-18
Phase 5 of the Mojo-native host plan: apps/blobs shipped as an image with no interpreter and no toolchain in it, proven on x86-64, gated on every pull request, and measured beside the Django demo. The same work settled…
- The Linux floor is the runtime's, not the build host's — 2026-10-06
A measurement from the engineering record. It corrects the known issue "The Linux wheel misses RHEL 9 by one glibc minor", whose stated closer — build inside a manylinux_2_34 container — would not have closed
- The fork audited — 2026-10-08
The review of the lightbug fork, 2026-10-07 and 2026-10-08: the method, the numbers before and after, which instrument found which record, and what is still open. It raised 76 records (LF1–LF76). 72 are fixed, two were…
- The host leaves the fork — 2026-09-18
DECISIONS D28 put host.mojo beside mojo_pool.mojo in the hard fork, because on Mojo 1.0 an application could not conform to a trait behind a .mojoc built from a directory named other than its package. Mojo 1.1.0 fixed…
- The layer moves to htmx 4
2026-09-19. Fragment[Htmx], page_or_fragment and apps/fragment_notes were gated against htmx 2.0.4, provisionally (DECISIONS D6: "moving the pin" was the retiring condition, and what would weigh was written down with…
- The loop on the wire: what each read and write rule answers — moved out of CLAUDE.md 2026-09-28
The two backends do not have the same trigger semantics, and every read path must satisfy the stricter one. add_read is EV_ADD on kqueue — no EV_CLEAR, so connection reads are LEVEL triggered and a partial drain is…
- The m0 wheel: source, an exact pair, and a CLI that refuses — 2026-09-20
Until this round an application in Mojo could be written in one place: apps/, inside this repository, against .mojoc files a poe task had built. This is the first of three pull requests that let one be written anywhere.…
- The Mojo host — built 2026-09-16
bus, accept sharing, signals and the drain, and a Python application brings only itself. A Mojo application had no host. Each one wrote its own main from the comments in CLAUDE.md's runtime constraints, and the…
- The Mojo stack's pages, and a front door CI walks through — 2026-09-20
The fourth round of the work that lets an application in Mojo be written outside this repository. The first three built the product — the m0 wheel (the-m0-wheel), m0 new and its templates (the-scaffold), m0 dev, m0…
- The page shell becomes a trait — 2026-09-18
DECISIONS D12 made the document shell a thin function over a separate context
- The picture on the wire — measured 2026-09-21
of Pro and into a free bundle. Rocket's own flagship example is a flow graph in which "the server owns the graph state while Rocket provides a canvas" — the same claim apps/blobs makes, and a shape blobs might adopt:…
- The pin moves to Mojo 1.1.0 — 2026-09-18
Mojo 1.1.0 reached PyPI on 2026-09-17. It carries fixes for two things this tree had built around: a PythonObject reference leak the WSGI bridge routes every request away from, and a witness-table bug that shaped where…
- The ramp test: lanes in the host, one module on two hosts — 2026-09-17
loop. m0serve serves a Mojo mount on MojoPool threads, N per lane. A views module written for one could not be promised to behave on the other: the same 200 ms compute view that a mount absorbs on one of four threads…
- The README points to the page that owns a fact — 2026-10-10
A record from the engineering notes. It holds the incident behind the README's rule, the rule, and the two halves of the rule no gate enforces
- The scaffold's upgrade path — 2026-09-27
A design note from the engineering record. SPEC N42 is the row and D52 the
- The scaffold: two templates that compile where they lie — 2026-09-20
The second of three pull requests that let an application in Mojo be written outside this repository. The first added the m0 wheel and the commands that need no template (the-m0-wheel). This one adds m0 new, the two…
- Threads first for m0 applications — 2026-09-25
The Mojo host has had two ways to more than one core since 2026-09-18: M0_WORKERS=N, forked processes under a supervisor, and M0_THREADS=N, N loops on N threads of one process. They measured the same, so the…
- Traceability: stable ids, then declared coverage
SPEC.md is a requirements traceability matrix -- the standard artifact in safety-critical software, whose defining property is that it traces BOTH ways: every capability to its evidence, and every piece of evidence back…
- Django views the right way, translated to Mojo, 2026-09-10
The article's claim is narrow and worth taking literally. A view is a function that takes a request and returns a response; the recommended starting point makes all three visible at a glance, and class-based views hide…
- The WebSocket close path RSTing instead of FINning — resolved v0.15.1
Listed as a suspected race for two sightings, both on macOS CI and never locally. It was not a flake: RFC 6455 §5.5.1 requires the endpoint that sends Close to WAIT to receive one, and the loop closed as soon as its own…
- The WebSocket send window — shipped 2026-08-28
The one path above that an ordinary application reaches, made to work rather than merely to fail honestly. websocket.send had no window: an app faster than its client filled the loop's 64 KB per-slot outbox and every…
- What the agent runs asked for — 2026-10-07
The follow-ups from the agent usability runs: the lines each page lacked, three changes to m0, and the two skills. Each stumble the runs counted became a line on a page, a change to the tool, or a line in a skill. SPEC…
- What the m0 wheel promises — 2026-09-29
A design note from the engineering record. D54 and D55 are the decisions; SPEC G9 and M14 move to out of scope. D56 joined them on 2026-09-30, under "libm0core"
- Workers, signals and the fork: why each process rule holds — moved out of CLAUDE.md 2026-09-28
install_shutdown_signals() returns the fd to pass as shutdown_read_fd; its handler writes one byte to that pipe and nothing else. Dispositions and fds are both inherited across fork(), so a pre-fork install points every…
- WSGI vs ASGI: the design record
The obvious roadmap item after a conformance-tested WSGI host is an ASGI host. This document is the case for not building one now. The short form: ASGI answers two independent questions — how does Python code overlap…