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 that and the pin moved the same day
(the-pin-moves-to-1-1-0). D28's retiring
condition then read "moving host.mojo and mojo_pool.mojo into src/".
This is the record of that round. One of the two files went where the
condition said. The other could not, and the reason is a second constraint
that the first had been hiding.
The probe
check-mojoc-trait's mismatch arm conforms an application struct to
m0_http.fragment.PageShell. That is the shape of AppHandler, and it is
not AppHandler. So before anything was committed, the host was compiled
into a throwaway package (mojo precompile of a directory holding
host.mojo, to m0_host.mojoc), and apps/host_check — a handler
conforming to AppHandler and a producer conforming to Producer — was
built against it with its one import line changed.
| step | result |
|---|---|
host.mojo precompiled as its own package, after m0_http |
compiles |
apps/host_check built against that .mojoc |
compiles, 716 KB |
| the binary, one request | 200, body host_check |
Both conformances cross a .mojoc boundary on 1.1.0 and the witness tables
exist at run time. PoolHandler has the same evidence in the tree as it
now stands: m0serve.mojo's MojoMount and HoldMount, the mount modules
and apps/pool_spike all conform to it through m0_http.mojoc, gated by
smoke-mojo-mount, smoke-hold-mount, smoke-mount-seam and build-apps.
What src/ cannot hold
host.mojo moved into src/ does not build:
Imported from src/host.mojo:147:
lightbug_http/event_loop.mojo:57:6: error: invalid Mojo precompiled file
'.../packages/m0-http/m0_http.mojoc': invalid magic bytes
from m0_http.log import log_access
The host calls run_event_loop. event_loop.mojo imports m0_http.log —
the fork's one standing back-edge, which CLAUDE.md says not to invert — and
m0_http.log resolves through m0_http.mojoc. That is the file mojo precompile src -o m0_http.mojoc has open for writing while it compiles
src/host.mojo. From a clean checkout there is nothing there; after a
build there is a truncated file. The cycle src/ → event_loop.mojo →
m0_http.mojoc has no first build.
Two ways round it were tried, because src/ already imports
lightbug_http (whose server.mojo reaches the loop) and builds:
| attempt | result |
|---|---|
from lightbug_http.event_loop import run_event_loop moved inside _run_loop[H], as Server.serve_nonblocking has it |
same error, reported from the local import's line |
a fork-side serve_on_loop[T] holding the local import, called from src/host.mojo |
same error, reported through server.mojo |
A precompile parses every body it reaches, local imports included.
Server.serve_nonblocking's local import works only because nothing in
src/ calls that method. So the rule is wider than the host: nothing in
src/ may reach event_loop.mojo, at any depth, for as long as
event_loop.mojo imports m0_http.log.
The placement on Mojo 1.0 was therefore over-determined. The witness-table bug was the recorded reason; this one would have stopped the move on its own, and nothing had ever asked.
What was built
mojo_pool.mojo is in src/, exported from m0_http (MojoPool,
PoolContext, PoolHandler, JOIN_TIMEOUT_NS). It names the
OffloadPool and the hold seam, never the loop, and it builds. Its import
of m0_http.threads became .threads.
host.mojo is a package of its own, m0_host, at
packages/m0-http/m0_host/. It sits above the fork and above m0_http and
is imported by neither, so its six m0_http imports are ordinary downward
ones through the .mojoc. It is resolved from SOURCE, as the fork is: the
directory carries the package's name, every task already passes -I packages/m0-http/, and no m0_host.mojoc is built or kept — a directory
beside a .mojoc of the same name shadows it, which is the trap CLAUDE.md
records for renaming src/. Three things follow from source resolution:
sabotage-host's smoke-gated rules edithost.mojoand run a smoke with no rebuild between, as they did in the fork. Behind a.mojoceach rule would have needed a precompile first.- An edit to the host reaches an application at its next build. An edit to
mojo_pool.mojonow does NOT, untilbuild-httpruns — and reachesbin/m0serveonly afterbuild-http,build-wsgiandbuild-serve. That is the opposite of the habit formed while the file was in the fork. - Lazy method bodies have the blind spot the fork's had, so
poe check-host-packagecompilesm0_hostwhole and throws the artifact away, insidetest-allbesidecheck-fork-package.
The fork's imports of m0_http went from eight, in three files, to one:
event_loop.mojo → m0_http.log.
The import paths, decided
The handoff named two shapes: a clean move, or a move with the fork's
__init__.mojo still re-exporting the old names. A re-export is an import,
so the second keeps the edge the move exists to remove. The clean move was
chosen, on these grounds:
lightbug_http.hostwas never in a release. The host landed after 1.4.0, so its path changes inside one[Unreleased]section.from lightbug_http import PoolContext, PoolHandlerWAS released, in 1.4.0, as the mount module's authoring surface (SPEC N14). It now readsfrom m0_http import PoolContext, PoolHandler. The served contract CHANGELOG states —m0serve's flags and environment, the two hold headers,m0pub.publish()— does not include a Mojo import path, and a mount module is compiled against the tree it is built with, so the break is a compile error naming the missing name at the build that adopts the new tree. It is recorded under Changed.- No application outside this repository supplies a mount module:
textshelf, the one production consumer, has no
.mojofile.
What apps/pool_spike still guards
build-apps compiling apps/pool_spike was the guard against moving
mojo_pool.mojo into src/: on 1.0 the app's PoolHandler conformance
failed there. The direction has reversed and the guard with it — the same
compile now fails if a toolchain takes the witness-table fix away, for the
real trait rather than check-mojoc-trait's PageShell.
What was not done
- The
m0_http.logback-edge stands. Removing it would let the host intom0_httpand end the cycle altogether. It is its own decision, the standing instruction is against it, and D33 records the constraint it now imposes. - D12 and D7 —
page_or_fragmenttaking aPageShell, andVocabularyopened to an application's conformance — are unblocked by the same pin and are each a round of their own. Neither touches the loop, so neither meets the constraint above.