main’s pipeline (read, check, lower) runs Nikaia end to endOpen and answer this issue on GitHub.
selfhosting, note · opened 2026-09-30 · updated 2026-10-25 · 7 comments
ADR-250 D5, road in ADR-294. Reached when read, check and lower run Nikaia from end to end, with Rust left only as adapters to crates (winnow, clap, toml, the file system).
Where it stands (0.0.426): 29.1 % of the toolchain is Nikaia — 17 297 lines in 59 files under crates/nikaia-std/src/tools/*.nika, against 42 178 lines of Rust in crates/nikaia{,-logic}/src (scripts/self_hosting.py).
Moved so far (see scripts/self_hosting.py for the list, by version): the syntax tree, the constant fold, the checker’s types and what it asks of them, the ledger (read, written, published), describe, the trait check, throws/locks/touch and their walks, views, text tiers, buffers (contracts::keep), the checker’s words, type and call helpers, NK1116, written-back expressions, the build-time integer (integers.nika) and value (build_values.nika).
Still Rust, largest first:
| Module | Lines |
|---|---|
check |
23 802 |
emit |
14 597 |
contracts |
4 926 |
parser |
4 377 |
project.rs |
3 519 |
bounds.rs |
2 723 |
prove.rs |
2 553 |
nikaia-logic |
2 184 |
build_time.rs (the evaluator’s walk) |
1 318 |
modules.rs |
1 105 |
diagnostics |
871 |
the rest (main, sysroot, text_tiers, grammar_run, manifest, describe, adapters) |
~4 000 |
How a move goes: one package per version, leaves first; a defect the move finds is fixed in the compiler in the same package (ADR-294 D3), never bent around in the .nika. Where the Rust reached for a wider number (i128) out of convenience, the move takes the better alternative instead (ADR-315’s checked_ names, integers.nika).
Generated by Claude Code
Where it stands (0.0.434): 30.1 % of the toolchain is Nikaia: 18 087 lines under crates/nikaia-std/src/tools/*.nika, against 41 907 lines of Rust in crates/nikaia{,-logic}/src (scripts/self_hosting.py).
Moved since 0.0.426: the basic bounds level (bounds_basic.nika, 0.0.428/0.0.429) and what a loop’s body does (bounds_body.nika, 0.0.431).
Next, as their own issues: #435 (the rest of bounds.rs: Shape, then the interval walk), then #436 (prove.rs).
Generated by Claude Code
Share: 26.3 % at the start of the day, 42.7 % now (scripts/self_hosting.py, 31 301 Nikaia lines). develop is green since 0.0.773 (clippy was red from the first move until then; CLAUDE.md now makes it a gate before every push). main follows through promote.
Moved today (the commits say what): contracts (trust, declared, ledger_items, tether report), diagnostics render, parser (numbers, parse errors, tree building, interpreter, parsed tree), views, the whole nikaia-logic crate (deleted, logic_*.nika), cexport (+ node.rs, deleted), the build_time evaluator, grammar_run/comptime_run drivers, bounds matched/ensured, proofs book, and about 2 850 lines of check/mod.rs into 18 check_*.nika files (the Rust Checker holds a CheckState as self.s).
Decided today: ADR-339 (the grammar language gets span(p), here, same_line; /// is a rule; no state, no extern rule) - build list on #557. ADR-094 D7 (NK2108, a part taken out of a mut parameter is given back) - built, #576. ADR-340 (every Expr node carries a parser-given id) - build list on #558, steps 2 to 4 pushed (0.0.777 to 0.0.779), see its last comment for what is left.
Compiler defects found by the moves and filed under this issue: #559 to #576 are fixed or no longer reproduce; open: #562 and #573 (decision), #578 (bool bound in a nested match lowers as &bool, ready).
What to do next, in this order
as *const MatchPattern / Stmt sites, Checked’s statement-byte + shape keys, then drop the node_of callbacks). It decides whether patterns and statements need an id.check/mod.rs, the rest: the takes cluster (taken, first_read_after, hands_over_path, the sequence-walk reports; about 3 000 lines) and then the recursive core (value_of, stmt, call, call_on, arguments) in one step. The Checker state is a Nikaia struct (CheckState); about 15 more fields still sit in the Rust struct (the i128 fields, the OnceCell, Parsed).emit/mod.rs (16 291 lines): the same way, after check. emit is almost all &self, so its leaves go first.parser/mod.rs, 3 185 lines) once span(p), here, same_line are built.comptime_run.rs (needs a Nikaia view of Parsed), bounds_report.rs, the IO parts of assets.rs, libraries.rs, proofs.rs, project.rs (4.2k), modules.rs, manifest.rs, sysroot.rs, main.rs.Honest scale: by the spike on #558 the walks and the emitter are about 77 % of check + emit; reaching 90 % of the toolchain needs all of the above, including project.rs and the grammar. Nothing in the language blocks it any more; what is left is volume, builds (10 to 30 minutes each on the shared machines) and the disk (it ran out three times).
How to work is in CLAUDE.md, section Moving Rust into Nikaia. The short form: one agent per large module, never two on one file, leaves first, clippy before every push, delete target/ of a worktree you do not use.
Generated by Claude Code
#580 is the decided build order for NK2302 precision (ADR-283 D25-D26, 0.0.836). A moved (ref self, world) method may hand back a view of world (or of self) once it is built, so no clone is needed. Until then, the .clone() workaround stays.
Generated by Claude Code
One lowered HashMap type for the tools (lowering defect D6) is decided in #581 (ADR-010 D9): one hasher per class of maps, decided after the whole package is read, and a type parameter at the package boundary, so a Rust adapter names a type that does not move (Names = keyed by default, Names<nikaia_std::hash::Fast> where it vouches for its keys). Until #581 is built, tools/*.nika keep BTreeMap/BTreeSet.
Generated by Claude Code
Can two builders run at once? Assessment of 2026-10-24 (architect; nothing started).
Two independent lanes are left.
check/*.rs (3,444 lines) and tools/check_*.nika.emit/mod.rs (9,594 lines) and tools/emit_*.nika. E9 is done (9 emit_core* files).CheckFacts, which both lanes only read.What blocks it, sorted into real and not real:
lower-std lowers the whole working tree, including the other builder’s half-edited .nika files. It then fails on them, or writes lowered/*.rs files the builder must not commit. The fix is a worktree per builder on its own branch, with the supervisor fast-forwarding develop. A checkout costs 35 MB.target/. The cargo lock makes the two builds wait for each other. The fix is a CARGO_TARGET_DIR per worktree: target/fast measures 3.0 GB (all of target/ is 7.5 GB) against about 20 GB free. The cost is one cold build (about 2 minutes) and one cold lower-std (181 G instructions, against 1.3 G when nothing changed). The machine has 4 CPUs, so two builds at once slow each other.function, program) and E10 (function, program, item) introduce the same bare names. Before the lanes overlap, #791 must be fixed, or both lanes must prefix every name.lowered.rs index (generated: re-run lower-std after a rebase); the self_hosting.py table (one row each); the version and CHANGELOG.md (the supervisor’s anyway); tests in one binary (each builder runs only its filter).Order:
Before stage 2, CLAUDE.md needs a worktree-builder rule: own branch, own CARGO_TARGET_DIR, rebase by the supervisor. It replaces today’s “no separate worktrees” rule.
Generated by Claude Code
Selective lower-std F.nika… as a way to run two builders in one checkout: assessed 2026-10-24, not filed as an issue.
Probe. I copied the sysroot (NIKAIA_SYSROOT) and gave one tools file a type error. lower-std then fails as a whole (NK1104 in assets.nika) and writes no file at all. So today another builder’s half-edited .nika blocks every builder’s lowering.
Why a file list cannot isolate the builders.
beside). The package ledger own is inferred from all bodies (infer_package_checked). So a file list still has to read the other files.HEAD. Even then, a change to a signature or a contract makes the other files’ lowered/*.rs stale, and the sysroot test fails on them.cargo build compiles the whole working tree, including the other builder’s half-done Rust adapter and lowered files. The target/ lock stays, and so does #791.The cost goal is already #667’s. The lowering cache key includes the compiler binary’s bytes, and the lowering runs in the binary. So every rebuild misses all files: about 181 G instructions instead of 1.3 G. Per-file keys on the file plus the package interface, then step 4, remove that miss. In a worktree, the path compiled into the binary (env!("CARGO_MANIFEST_DIR")) is a second reason every key misses.
Recommendation.
Generated by Claude Code
End point, owner’s direction (2026-10-25): when self-hosting is finished, the compiler becomes a Nikaia package of its own. Its nikaia.toml names tools/*.nika as its sources, it is built by nikaia build, and the compiler tests (today tests/compiler) become that package’s tests/. Nothing is built for this before the end of self-hosting; until then the Rust workspace and tests/compiler stay as they are.
Generated by Claude Code