m0serve Realtime from a synchronous Python app

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" below.

The question

The m0 wheel ships the framework as source: every tracked file of m0-core, m0-http, the fork, the host, m0-datastar, m0-sqlite and m0-postgres (D39). A name in one of those trees is something an application can import, so it reads as something the packages promise. The 2026-09-28 review went through them asking what uses each, and found a set of names that nothing in the tree calls — not m0serve, not the Mojo host, not an application under apps/, not a scaffold template — each with tests of its own and, in two cases, a place in the documentation:

  • RequestContext (request_context.mojo), a carrier from before_request to after_response that no handler built.
  • check_api_key (auth.mojo), and with it AppConfig.api_key and M0_API_KEY. The configuration read the variable and nothing read the field, so no request was ever refused by it; the host's doctor went out of its way to leave the key out of its report.
  • ResponseCache (response_cache.mojo), a URL-keyed cache.
  • PatchJournal and JournalResult (sse/journal.mojo), a per-URL patch journal. DatastarStream's own replay journal is a different thing and stays (SPEC I10, I30).
  • negotiate_encoding and negotiate_language, and the free functions wants_html and wants_event_stream in content_negotiation.mojo. AcceptResult and its fields stay; applications read accept.wants_html.
  • FNV-1a and xxHash32 in m0-core's hashing.mojo, with the 32-bit hex formatter that printed them, and their C-ABI exports m0_fnv1a, m0_xxhash32 and m0_format_hash. libm0core stays, because m0pub numbers its events through its one other export, m0_shared_fetch_add.
  • run_benchmarks.mojo, m0-core's benchmark, which the review found no longer compiled, and its bench-core task.
  • m0_sqlite.stats_ints and its siblings (reduce.mojo), whose one user was bench_sqlite.mojo.

ResponseCache and PatchJournal are the supports of a Siren/GRAIL server, and sibling repositories still use copies of them. The owner's decision of 2026-09-29 is that Siren/GRAIL on m0 is not a product aim.

What was decided

The packages ship what the application layer and m0serve use. Each name above left the tree, with its tests. One moved rather than left: stats_ints, with ColumnStats, went into bench_sqlite.mojo, so the aggregate figure in docs/SQLITE_PERFORMANCE.md stays reproducible, and the bench refuses to time an answer that differs from SQLite's own, which is the check that mattered. SPEC G9, API-key authentication, moved to out of scope: an application authenticates in its own views, with the layer's signed session and CSRF check (N13, N43) or a grant (I21), and a key checked in front of the server is a proxy's.

Nothing served changes. m0serve never acted on M0_API_KEY, and none of the removed names was reachable from it or from the Mojo host. What changes is what an application built with the m0 wheel can import, and the wheel is a 0.x preview (D43): an application that imported one of these names keeps a copy of its own.

The client

m0_http.Client (client.mojo), an HTTP/1.1 client over the fork's own connect, encode and parse, went the same way, with its end-to-end check and its smoke (SPEC M14). No application called it. It spoke no TLS, so it could reach almost nothing outside a private network. And it blocked: a view that runs on the loop and makes a call stalls every connection that loop holds until the answer comes back, and where a call should run — a pool thread, or the loop without blocking it — was never decided. A gate kept it compiling and answering, which is not the same as it being usable.

Outbound calls are designed later, as a phase of their own (D55), not kept alive by a gate on a client nobody used. That design answers TLS, where the call runs, and connection reuse. The fork is untouched: the pieces the client assembled stay where they were.

libm0core, 2026-09-30

D54 kept libm0core for m0_shared_fetch_add, the call m0pub makes through ctypes to number the events it publishes. Every release still attached it as a standalone download, bare and as a self-contained bundle, though no caller outside m0serve was known. It is m0serve's private runtime and not a published artifact (D56). The release workflow stopped attaching it, poe bundle-ffi and its CI step left, and the library ships inside the m0serve wheel only, in _lib/ beside the binary, where the wheel's launcher points m0pub at it. Nothing changes for an m0serve user.

The evidence: across 32 releases its 114 assets were downloaded 32 times, and 0 times on each of the seven releases from v1.2.0 to v1.8.0; no project outside this repository names it, apart from a vendored copy of this repository; and m0pub is the only caller of its only export. docs/FFI_DISTRIBUTION.md has the history of the bundle and what carried over to the wheel's copy, including the self-containment check.

What would retire it

For D54: Siren/GRAIL returning as a product aim on m0, which brings its supports back with it; or an application on the layer that needs one of these names and cannot keep its own copy. For D55: an application on the layer that needs outbound calls. For D56: a named caller outside this repository that asks for a C ABI, at which point publishing resumes with a versioned ABI.