A from-scratch, non-Unix, ring-0 operating system in Rust, built around a language model that runs inside the kernel.
Inside the kernel there is no privilege boundary and nothing to marshal across one. The consequence that matters is what a tool call becomes: the sampler picks a name from the live applet table under a grammar compiled from that table, and the kernel calls the function. Nothing is serialised and nothing is parsed in between.
There are two stories on this machine and neither is a retreat from the other. The kernel has no boundary because removing it is the whole idea. A program written somewhere else has not earned the machine, so it gets ring 3, its own page-table root and a syscall surface -- 95 of Linux's roughly 350 for 64-bit programs and a table of its own for 32-bit ones, enough that unmodified busybox, glibc's dynamic linker, SDL2 programs and Mesa all run here without knowing where they are. Mesa is the interesting one: through a render node this kernel serves, it does Vulkan and OpenGL on a real graphics card, and nothing in the kernel knows what Vulkan is.
It boots as a UEFI application, which means it is the kernel. Firmware
already delivers long mode, CPL 0 and an identity map, so the boot path has no
bootloader, no ELF loading, no relocation and no handoff ABI. The ELF loader
that exists is for running other people's programs rather than for starting
this one. The model, tokenizer and root certificates are read before
ExitBootServices, because that call is the last moment a filesystem exists.
313 files of Rust, about 241,000 lines, of which about 25,000 are a symbol
table generated from the linker map. What is in the kernel that this project
did not write is Rust's core and, for fonts only, fontdue and the crates
beneath it, all vendored and listed in NOTICE.md. TempleOS is the obvious
ancestor, and the identity map here exists for the reason Terry Davis gave for
his.
Beside the model runs a real animal's nervous system: the adult fruit fly from the FlyWire connectome, 138,639 neurons, simulated spike for spike and fed the machine's own events as senses. Its mood colours the desktop, changes the model's manner and wears down the mask on its voice.
A research operating system, developed on an MSI Thin GF63, built to answer one question: what changes when a language model is a kernel primitive? The table is what works. The list after it is what does not, which on a project like this is the more informative half.
Works, and verified:
| Boot | UEFI application, own page tables, GDT/IDT, APIC timer, i8042 keyboard |
| Memory | Physical frame allocator, identity paging to 4 GiB, coalescing heap |
| Tasks | Cooperative and preemptive at 100 Hz, sysv64 context switch |
| Ring 3 | Guests at CPL 3 on a page-table root of their own, entered by iretq and left by sysretq, with a guest fault ending the guest and not the machine |
| Linux ABI | 95 syscalls for 64-bit programs and a separate i386 table for 32-bit ones: fork, execve, wait4, threads and futexes, signals with a real ucontext on the guest's stack, pipes, epoll, Unix sockets with SCM_RIGHTS, memfd, /proc, /dev/fb0, evdev, ALSA |
| Ported | Unmodified busybox under both musl and glibc, QuickJS, SDL2 programs, 32-bit glibc programs, Mesa |
| GPU | /dev/dri/renderD128 speaks Linux's virtio-gpu interface; unmodified Mesa Venus does Vulkan and Zink does OpenGL 4.6 on the host's GPU, from ring 3 |
| Jobs | Guests run as background jobs beside the shell (linux bg, jobs, kill), several sessions at once, each with its own process IDs, their threads spread over every core |
| Wayland | Skywalker, a Wayland server inside the kernel: windows, damage, frame callbacks, xdg_shell, a keyboard and pointer, full screen, pointer lock, and dma-buf feedback naming the render node |
| Launcher | Installed programs are desktop icons: an entry file on any FAT volume names the runtime, the files and the icon, and a double-click runs it |
| Graphics | A compositor that repaints only what changed: anti-aliased shapes, OKLab gradients, glass, shadows, tiling and animated windows, Flip 3D, eleven apps, a wallpaper that follows the brain's mood |
| Text | Noto Sans and Cascadia Code through fontdue, with Noto's kerning; a 325-glyph 8x8 bitmap font for boot and the fault path |
| Storage | NVMe, content-addressed object store, Merkle trees, snapshots, ranged write gate |
| Network | ARP, IPv4, ICMP, UDP, TCP, DHCP, DNS, TLS 1.3 with chain validation |
| Drivers | e1000, RTL8168, xHCI (USB 3), CDC-ECM USB Ethernet, USB keyboards and mice, HD Audio, virtio-gpu, the Intel AX210 family up to a scan |
| Crypto | SHA-1/256/384, HMAC/HKDF, AES, ChaCha20-Poly1305, X25519, RSA, ECDSA |
| Model | Qwen3, Qwen3.5 hybrids and SmolLM2, int8, in-kernel inference |
| Routing | Constrained decoding over the live applet table, plus a closed-form probe |
| Agent | Propose, validate, execute, observe, with the grammar as the permission system |
| Initiative | A resident task that decides for itself when to act, when to speak up, and when to stay quiet |
| Conversation | Plain language at the prompt: requests become exact commands offered with y/n, questions are answered from the machine's own clock, memory and files, and what you tell it about yourself is kept |
| Brain | The adult fruit fly (FlyWire, 138,639 neurons) or a compiled-in worm, as integer spiking neurons matching a Python oracle spike for spike; senses from OS events, neuromodulators, sleep, and an affect the rest of the machine reads |
| Voice | Kokoro-82M in the kernel, checked stage by stage against the reference; HD Audio output with volume, shared with guests through ALSA |
| Research | A ledger that takes a hypothesis and its predictions before the experiment runs, and a preregistered introspection test |
| Language | Aiksi: lexer, parser, tree-walking interpreter, records, types, capabilities |
| Codegen | An x86-64 back end for integer functions, calls and recursion included, checked against the interpreter on value, step count and error text |
| Formats | Detection and structure for text, markdown, json, jsonl, xml, csv, ini and eight languages |
| Power | Digital thermal sensor, measured frequency, HWP governors, all behind a CPUID gate |
| ACPI | An AML interpreter: namespace, evaluator, operation regions, embedded controller |
| Battery | Charge, health and time remaining from the firmware's own bytecode, and S5 power-off |
| Training | Gradients, Adam, QDoRA over the classifier and over every q/k/v site |
| Self-modification | Variant lineage, a judge council, an append-only ledger, O(1) rollback |
| Updates | Signed staged images swapped before ExitBootServices, with rollback |
| SMP | Per-core GDT/TSS/APIC, a real spinlock, shared heap and console; guests at ring 3 on every core, the kernel side of them under one lock |
| Accounting | Every allocation billed to the task that made it, with peak and outstanding |
| Mining | SHA-256d, yespower, NeoScrypt and BLAKE2s in ring 0 against upstream's own vectors; four coins at once on parallel slices, standing down for the model |
| Pool | Stratum V1 upstream, one downstream connection carrying several coins, shares validated by recomputation, PPLNS on a ledger that survives a restart |
| Self-repair | A failing boot check marks its subsystem unavailable instead of halting; a repair is chosen by the fault's own signature, judged by re-running the check, and survives a reboot -- withdrawing itself if it prevents one |
| Faults | Reports name the function, from a 24,844-symbol table built out of the linker map |
| Guests | Names the hypervisor from its PCI ids and says what it has configured wrongly |
Does not work yet:
- SATA. There is no AHCI driver, so a machine whose disk is SATA reaches a shell with no store, no snapshots and nowhere to save. It is the largest hole in the device table and entirely tractable -- AHCI is published and needs no firmware. It matters most in a virtual machine, because VirtualBox and VMware both default to SATA; set the controller to NVMe and the guest will tell you if you forgot.
- Joining a Wi-Fi network. The laptop's Intel card boots its firmware, reads its NVM and scans: nineteen networks on the first try. Joining one does not work yet; the firmware asserts while the station context is being set up. The WPA2 supplicant is complete and checked against IEEE 802.11i vectors at every boot.
- Kernel tasks on more than one core. Every core has its own descriptor
table, task-state segment, per-core block and idle task, and the scheduler
carries a task between cores. What runs on all of them is guests: a
program's ring-3 threads spread over every core, while the kernel side of
every guest syscall takes one big lock, so the roughly two hundred
Racystatics behind it are still reached by one core at a time. That is the old big kernel lock, deliberately -- auditing each of those statics is the work still owed. Kernel tasks stay on core 0, apart from the mining slices and the brain, which are placed on cores of their own. - A native GPU driver. The graphics card is reached through virtio-gpu, which means a virtual machine on a Linux host with the host's GPU behind it. A driver for the laptop's own NVIDIA part on bare metal is the next step.
- A 3D program's first frame, about half the time. It stops inside frame presentation, waiting on the host for a present that never completes. Restarting it usually gets past it. Frame rates were measured standing still, not moving through a scene.
- A hardware entropy source. The generator is a fast-key-erasure ChaCha20 DRBG fed by keyboard and mouse interrupt timing and by NVMe completion latency, and it refuses to answer for key material until it has seen enough events. That refusal is correct and it is also a limitation: a machine that boots, touches no disk and shuts down with no key pressed never seeds, and TLS then falls back to timing-derived keys and says so during the handshake. The CPU has RDRAND and this does not use it, because trusting an opaque instruction is a different argument from trusting interrupt timing.
- A fault report on the framebuffer. Painting from inside an interrupt gate raises a general protection fault here, so the report goes to the serial port in full before the console is attempted at all. On the laptop there is no UART, which means a fatal fault there currently prints one line and halts. The bug belongs to the console and it is now visible instead of silent.
- An authored application reaching adoption. The machine writes drafts and never adopts one. The planner's output is likewise stringified into a report rather than gating how much the loop attempts.
- A model-invoked program in the background. The shell can put a guest in the background; the agent cannot yet, so a program the model runs holds the terminal until it exits.
-
The brain. A connectome runs as integer leaky integrate-and-fire neurons in its own task, matched spike for spike against
tools/connectome.py. The adult fly ships on the 0.6B image and the worm is compiled in. Keyboard and mouse are touch, being spoken to is hearing, a job done well is reward, a fault is pain, a hot CPU is heat. Its readouts become valence and arousal; its own aminergic cells set dopamine, octopamine and serotonin; it sleeps. -
What reads it. The desktop (a white test chamber when content, drained to monochrome and scanlines when upset, an eclipse asleep), the model's manner (from curious to an overseer who promises the testing will never end), the voice's mask, and when the machine speaks up on its own. Nothing it reads decides what the machine is allowed to do, and every judge the machine uses on itself sees it as neutral.
-
Watching it.
brainat the shell, or the Oracle's Brain tab: how it feels and why, what it senses, what it is driven to do, its chemistry and its mood over the last two minutes.brain poke painto try it. -
A finding. The published fly model holds a heat- or smell-ignited state of about 10,000 neurons that never ends, carried by a 477-neuron core in the antennal lobe found from the wiring alone. Short-term synaptic depression ends it.
design/attractor.md. -
The voice. Kokoro-82M in the kernel, with a rule-based style over it: composed it is a machine, and as the mood drops the mask slips.
volume, the volume keys, or Settings > Sound.
Nothing here claims the machine is conscious or feels anything.
design/indicators.md holds the measurements, preregistered before their
first run.
The capabilities above that are newest deserve a paragraph each, because together they are the point of the project.
The model trains inside the kernel. train adapter fits a QDoRA adapter
over the classifier from the corpus in the namespace. The base weights stay
frozen, so every hidden state is a constant and gets cached once per example;
an epoch after that costs no forward passes at all. Restricted cross-entropy
zeroes the gradient outside the grammar's candidate set, so the only rows that
move are ones the decoder can reach. On SmolLM2 that is 132 rows out of 49,152,
which is what makes the whole exercise affordable on this hardware. Real-corpus
training refuses to run without AVX2, because scalar emulation would turn every
hyperparameter judgement into a judgement about the clock.
Training goes deeper than the classifier now. deeptrain moves every q/k/v
site through a taped forward pass and a backward walk over the layers. The
economics are different enough to deserve a different command: the classifier
path caches hidden states once and an epoch costs no forward passes, while
moving a projection in layer three moves every state after it, so every epoch
pays a forward pass per example. Measured under emulation on SmolLM2, 24
forward-and-backward passes in 110 s.
It goes through the judges as well, and how is the interesting part. Those judges rest on cached features and a deep adapter moves the features, so a trial prepared beforehand cannot judge what came out of it. Re-preparing one afterwards does not work either, because decisions are recorded along the baseline's own decode path and a change that alters that path alters how many decisions there are. The comparison pairs on routing instead: one entry per example, the same examples both times.
A trained adapter is an object in the namespace. adapter save writes a
GLADOSA1 blob holding the adapter alone, never the checkpoint. Rows are
stored sparsely, because they are sparse in fact: on the measured decision
layer that is 23.7 KB against 1.79 MB dense, a factor of 75. Save, detach,
reload and save again reproduces the file byte for byte, which is checked by
comparing content addresses. tools/adapter.py reads the format on the host.
Self-modification runs on certificates. Schmidhuber's Gödel machine adopts a rewrite once it can prove the rewrite raises expected utility, and it carries a theorem prover to do it. There is no theorem prover here and none could be built for a quantised transformer's future reward. What is substituted is a certificate that is cheaper to refute than it was to produce, over content-addressed inputs, so any later run re-derives the same verdict bit for bit. That is affordable for one specific reason: producing a variant costs a forward pass per example, and checking somebody else's claim about one costs a dot product per cached decision. Four orders of magnitude between making a claim and testing it is the asymmetry a proof system provides, reached by another road.
Four judges have to agree, each covering a different way a variant can be bad:
- J1, margin. Both variants answer the same cached decisions, so the comparison is paired and McNemar's test applies. Nine repairs against two breaks scores 3.27 and fails the bar; twelve against one scores 7.69 and passes. Comparing two percentages would call both of those a win. The floor is symmetric, because "not significantly worse" alone once adopted a routing rule on a measured four repairs against ten breaks.
- J2, its own goals. Eight goals the machine sets itself, each declared
with the applet it ought to reach, are decoded again under the candidate. A
goal the incumbent already routes wrongly is not protected, and no goal may
reach an applet that changes things. A variant that reroutes "list the files
in /ai" to
mvis catastrophic, aggregate accuracy would never show it, and the transcript now names where the goal went. - J3, structure. Finite factors, positive scales, finite logits across every cached decision. This catches the variant whose validation accuracy improved while carrying a scale that overflows on the first unfamiliar prompt.
- J4, cost. Rank and resident bytes. The heap is one physically contiguous allocation on a ladder, so a lineage that grows with every adoption is a lineage that eventually fails to boot.
Adoption is a pointer swap and the parent stays addressed, so rollback costs a
pointer write. Every trial appends a line to /ai/godel/ledger.txt whether it
was adopted or rejected, with all four judges' numbers in it.
The night branch rotates over every axis that has a judge. It knew two jobs and one always won the tie, so the adapter grid was walked to exhaustion while the routing rule, deep training, a compiled skill and a core the machine wrote were never tried unattended at all.
The rotation is a ranking rather than a round, and what it ranks by is worth
stating: next_proposal reaches for the axis whose next verdict the ledger can
least predict. An axis that has said yes to everything and one that has said
no to everything are equally predictable and equally uninformative, so a
Laplace-smoothed Beta posterior folded to a distance from the coin-flip puts
the axis nearest 50% first. Fairness is traded for information deliberately.
Smoothing is what stops that becoming starvation, ties break by slot so the
order is total and re-derivable, and the composed core is exempt and always
last, because producing its candidate costs a dozen decodes and is reached for
when everything cheaper is out of moves.
Two details that are load-bearing. The test slice carries a budget that lives in the ledger, because a loop which improves itself forever reads the held-out set forever and each read makes the reported figure more optimistic; past the budget a test number prints as stale and is marked unquotable. And before the judges run, the machine records whether training-set gain predicted a win, so the ledger accumulates an answer to a question worth settling. Nothing acts on that prediction, and at the current sample size it means nothing, which the ledger says out loud.
Trials run when two independent facts agree: the real-time clock says the hour falls inside a quiet window, and the entropy ring says no key or pointer interrupt has fired. A clock alone does not know somebody is working late, and silence at noon is a coffee break.
Skills are judged before they are adopted. A successful episode compiled into a program used to be adopted by the act of writing it. A candidate is now stored by content address and put through four judges of its own: it parses, it runs under the powers an unadopted skill actually has, it repeats with the same value and step count and touched set, and it is cheap. Trust is keyed to the hash of the file, so editing a trusted skill revokes its trust by construction, and granting trust is a shell command that is never an applet.
src/linux/ began as a measuring instrument rather than a loader. The
expensive unknown in porting the Linux ABI is not the loader, which is a
weekend; it is that Linux has no specification you can test against, so a
subtly wrong mmap flag surfaces as a crash in a different subsystem an hour
later. gVisor needed 237 of Linux's roughly 350 calls to run containers. Before
committing to a privilege model or a syscall subset, the thing worth owning is
a trace of what a real binary actually asks for -- so every unimplemented call
is recorded and refused with -ENOSYS, and a run that ends there has named the
next call to implement.
The first sixty-two were not guessed. Sixteen busybox applets were swept
under musl and twenty-eight more under glibc, every gap they named was
implemented, and nothing else was. The thirty-one after those are the one place
this discipline was relaxed, and it is worth saying which way: processes,
pipes, epoll and memfd were implemented for targets that have not run
here yet -- Wine's server loop and a Wayland client -- rather than because a
trace demanded them. The Wayland client and SDL2 have since run and been
confirmed; Wine has not. The shape of the findings was the useful part twice over. ls ran perfectly on its first attempt -- opened the
directory, walked it, lstated every entry, exited 0 -- and printed nothing at
all, because everything it had to say went through writev. A program that
works and is silent is the worst shape a missing syscall can take.
The evidence that this is real rather than a demo is a hash:
linux run /tmp/busybox sha256sum /tmp/busybox
b01eaede758499526db8c8ccd159b0f773ef0ecb29c25952e5c1042f5168e4ec
That is the digest the development machine computes over the same bytes. A
dynamically linked binary relocated a 1.9 MB libc through this kernel's mmap,
hashed its own file at ring 3 under a private page-table root, and got it right.
Which libc is not a decision this kernel makes. A libc is userspace: it is
linked into the binary or named by it in PT_INTERP, and the loader reads that
path and loads whatever is there. So musl and glibc are both installed and each
program takes its own -- which works today and needed no code. Writing a dynamic
linker was never the answer; loading ld.so was, and dlopen then becomes its
problem rather than ours.
fork needed two address spaces, and one address space was the founding
claim of this system rather than a shortcut it took. src/mem/space.rs is
what removed it. The decomposition that made it small is worth keeping: only
the image has an address the file demands, because the stack, the break and
every mmap are at addresses the kernel chooses. So 0x400000 is the whole
problem, and it is one region.
A guest fault ends the guest and nothing else:
linux run /tmp/wild
ring 3, one address space -- only its own pages carry the U bit
killed by fault 0x0e after 0 syscall(s), machine intact
At ring 0 the same fault stopped the machine, because a guest sharing an address space with the kernel might already have corrupted anything. At ring 3 the kernel is intact by construction, so ending the guest is the honest response.
What this does not get you, said plainly. There is no isolation from a hostile guest worth the name, and stage 0 said so: contained bugs, not malice. A guest's threads do run on every core now, but the kernel side of all of them is serialised behind one lock. A guest that makes no syscalls receives no signals, because delivery happens on the way out of one -- a program spinning in a loop cannot be interrupted, which on Linux it could, and the run deadline is what ends a runaway instead.
Every OpenGL on Linux is a userspace library, and so is every Vulkan
driver. That fact makes graphics mostly not kernel work. /dev/dri/renderD128
speaks Linux's virtio_gpu interface: GEM buffers, blob mappings, context
creation and command submission. Unmodified Mesa Venus, running as an ordinary
Linux program, does all of Vulkan through it, and under QEMU with KVM that
reaches the host's RTX 3050. A million-element compute dispatch comes back with
every element checked. Mesa's Zink puts OpenGL 4.6 on top of that Vulkan, so a
32-bit GL program reports zink Vulkan 1.4 (NVIDIA GeForce RTX 3050) and draws
in a Skywalker window at about 50 frames a second.
A 32-bit 3D game went from about 1 frame a second on the CPU to 27 to 38 on the GPU. Four things decided it:
- The host has to honour the guest's cache types. NVIDIA's write-combined
memory is written by the GPU without snooping. Under QEMU's default every
guest page is write-back, a CPU read gets stale lines, and about a fifth of a
compute result came back as the pattern written before the dispatch.
-accel kvm,honor-guest-pat=on, and each blob is mapped with the type the host reports. - EGL has to find a GPU to ask for, so Skywalker advertises
zwp_linux_dmabuf_v1with a feedback naming the render node. Zink then presents through Venus's Wayland WSI ontowl_shm. - A sleep may not run short.
nanosleeprounded anything under a 10 ms tick to nothing, and Venus gives up on a host ring after 1024 waits it believes took seconds. - A submit must not hold every other thread. Waiting for the host with the guest lock held made every Venus notify a round trip for the whole program, and that one change, with the compositor being woken at once, took the game from 19.6 frames a second to 27 to 38.
The host's ring threads sometimes go to sleep with commands unread, and the cause is not found. The kernel sees every ring Venus creates, so a watchdog notifies any ring that has work and has not moved across two looks.
Installed programs are desktop icons. An apps/*.app file on any FAT
volume names the program, its runtime, what to mount and copy, its environment
and its icon. Boot puts it on the desktop and in the Program Manager, a
double-click runs it as a background job, and gpu=yes adds the Zink
environment only when a render node exists. runtime= is the seam a Windows or
BSD runtime is meant to plug into later. A program already running is not
started a second time.
src/aiksi/ is the language everything above the kernel is written in, and the
intended relationship is C to Unix or HolyC to TempleOS. A program is
code.ai&xi, an extension chosen because nothing on the host has it.
use "/lib/text"
rec Host { name: str, port: int }
fn reachable(h: Host): int {
if (tcp_connect(h.name, h.port, 600)) { tcp_close() return 1 }
return 0
}
Records are values, so b = a copies and there is nothing to say about
aliasing. Types are optional and never inferred, and they are checked where a
value crosses a boundary somebody annotated. use is textual inclusion that
happens once, and the imported program runs with the importer's capabilities,
so an import can never be an escalation.
BUILTINS is an allowlist and that is load-bearing. Every row is a name, a
touch class and an arity range, and dispatch is refused for anything absent
from it before the arguments are examined. An arm added to the match without a
row is unreachable dead code; a row without an arm answers "no implementation",
which is broken and not dangerous. It replaced two denylists that were correct
until the language was wired to the network, and a denylist grants by default,
so the builtin anyone forgets is the one that matters.
A code generator exists for the integer subset. src/aiksi/jit.rs compiles
a program of integer functions -- arithmetic, if, while, return, and calls
between them, recursion included -- to x86-64, emits it into a page-aligned
heap buffer and enters it through a sysv64 pointer. On fib(18) it is 52
times faster than the tree walk, agreeing with it on the value and on all
83,606 steps.
Everything outside that slice is refused rather than approximated. It runs only
inside the differential harness and never on a live path, because a code
generation bug in a ring-0 image with no fault recovery gets exactly one
mistake.
What made that measurable came first. An Aiksi step costs about 14 ns, measured
for the first time by core bench, and a routing vote spends 20 steps against
a budget of 20,000. So the tree walk was never the cost the plan assumed, and
three rounds of removing allocations instead made a vote 2.9 times faster: the
kernel record table was being rebuilt per interpreter, a function's whole AST
was deep-copied per call, and a core's declarations were re-run per vote.
Developed against an MSI Thin GF63 12UC (board MS-16R8). That is the only machine it has been meaningfully tested on.
It should boot on most x86-64 UEFI systems, since the graphics path is plain GOP and the boot path assumes nothing vendor-specific. Storage and networking are a different matter, because a driver has to match a chip, and the memory-map handling has been tuned against one firmware. Expect a shell and a working model on other hardware, and treat your disk and your network card as open questions.
The boot disk on the development machine is counterfeit: it advertises
976 GB and holds 14.67. That is why the layout tooling uses MBR, a GPT backup
header having no real flash to land in, and why it carries a SafeLimitGB.
The model is not in this repository. It is around 570 MB, it is not this project's work, and a git repository is the wrong place for it. The ISO ships with one; building from source means supplying your own.
The kernel reads three files from the EFI System Partition:
<ESP>/EFI/BOOT/BOOTX64.EFI the kernel
<ESP>/GLADOS/model.bin the model, converted
<ESP>/GLADOS/tokenizer.bin the tokenizer, converted
<ESP>/GLADOS/roots.der root certificates (optional)
<ESP>/GLADOS/voice.bin the neural voice (optional; tools/kokoro.py)
<ESP>/GLADOS/brain.bin the adult fly (optional; tools/connectome.py build fly)
<ESP>/GLADOS/FW/ Intel Wi-Fi firmware (optional; tools/wifi_fw.py stage)
Without roots.der, TLS still encrypts but authenticates nothing.
Download a checkpoint and convert it:
huggingface-cli download Qwen/Qwen3-0.6B --local-dir tools/qwen3
python tools/convert.py tools/qwen3 esp/GLADOS/model.bin --seq 512
python tools/tokenizer.py tools/qwen3/tokenizer.json esp/GLADOS/tokenizer.bin --verify--seq sets the context window, and what bounds it is KV cache size instead of
the model. Qwen3-0.6B costs 112 MiB of kernel heap at 512 tokens, and
convert.py prints that figure so the decision gets made where --seq is
chosen, before it can surface as an allocation failure at boot.
Always pass --verify to tokenizer.py. It reimplements the kernel's
algorithm and diffs it against the reference tokenizers library, token for
token. A tokenizer that is subtly wrong produces text that still looks like
text, so nothing downstream will catch it for you.
Three families load. convert.py dispatches on model_type: llama, qwen2
and qwen3 take the dense path and produce a v3 file, while qwen3_5 produces
a v4 file with a layer-major body, because three layers in four hold a gated
DeltaNet mixer and the fourth holds full attention. Qwen3.5-MoE is refused at
load with LoadError::Unsupported, since the smallest published one is 71.9 GB
and nothing that size reaches a UEFI pool on this laptop; a forward pass for it
could never be run and contradicted, so none is written.
SmolLM2-135M also works, and it is the quickest checkpoint to develop against.
The release ISO for the 0.6B stamps an 8,192-token context and carries the voice and the fly; it wants 8 GB of memory. Every image carries its Wi-Fi firmware, so there is nothing to download beside it.
Rust nightly targeting x86_64-unknown-uefi:
cargo build --releaseThe artifact is target/x86_64-unknown-uefi/release/glados.efi.
Download the ISO from releases and give the guest these four settings. Three of them are the difference between a working machine and a shell with nothing behind it.
| setting | value | why |
|---|---|---|
| Firmware | UEFI, not BIOS | This kernel is a UEFI application. There is no BIOS path and never will be -- UEFI already hands over long mode, CPL 0 and an identity map, which is why there is no bootloader here at all. |
| Disk controller | NVMe | There is no AHCI driver. A SATA disk is a disk this kernel cannot see, so the store, snapshots and anywhere to save all quietly do not exist. VirtualBox and VMware both default to SATA. |
| Memory | 4 GB or more | The weights are read whole into a pool before ExitBootServices. A 0.6B checkpoint is about 600 MB and the 2B is 1.9 GB. |
| Network | Intel PRO/1000 (e1000) or e1000e | Those are the two the e1000 driver claims. Anything else is listed as present and undriven. |
Two or more cores is worth it: diag mt and diag migrate are false
statements about a single-core machine and correctly fail there.
The guest tells you when one of these is wrong. devices names the
hypervisor from the PCI ids it can see and then says what needs changing:
glados> devices
running on VirtualBox
no NVMe controller -- set the guest's disk controller to NVMe; there is no
AHCI driver here, so a SATA disk is a disk this kernel cannot see
(a SATA controller in AHCI mode is present and is exactly what cannot be driven)
It names the machine from devices rather than from CPUID, and the reason is
worth knowing if you are comparing notes: QEMU reports itself as
VMwareVMware at CPUID leaf 0x40000000. That is its vmware-cpuid-freq
property, on by default, which borrows VMware's convention for the leaf that
reports TSC and APIC frequency. Both readings are printed when they disagree.
qemu-system-x86_64 \
-machine q35 -cpu max -smp 4 -m 4G \
-drive if=pflash,format=raw,readonly=on,file=/usr/share/OVMF/OVMF_CODE.fd \
-drive if=pflash,format=raw,file=OVMF_VARS.fd \
-cdrom glados-1.4.3-qwen3-0.6b.iso \
-drive file=store.img,if=none,id=d0,format=raw \
-device nvme,serial=glados,drive=d0 \
-netdev user,id=n0 -device e1000,netdev=n0OVMF_VARS.fd must be a writable copy, not the system one. Create the
store with qemu-img create -f raw store.img 8G; without it the machine boots
and has nowhere to keep anything. Add -accel kvm on Linux, or -accel whpx
on Windows; either is about 160 times faster than the interpreter.
- System → Motherboard → tick Enable EFI (special OSes only)
- Storage → remove the SATA controller, Add Controller → NVMe, then attach the ISO and a new disk to it
- Network → Adapter 1 → Advanced → Adapter Type: Intel PRO/1000 MT Desktop
- System → Processor: 2 or more
In the .vmx, or through the GUI equivalents:
firmware = "efi"
nvme0.present = "TRUE"
ethernet0.virtualDev = "e1000e"
memsize = "4096"
numvcpus = "4"
None of this has been booted in VirtualBox or VMware. The device
identification and the advice above are asserted against synthetic device lists
in diag devices -- nine claims covering both vendors -- and the settings are
derived from what the kernel requires rather than from a run somebody did. If
one of them is wrong, the guest will say which subsystem it is missing, and
that report is worth more than this table.
python tools/drive.py "initiative off" "agent stop" "diag all"drive.py boots QEMU, stages the binary, resets NVRAM to pristine, and drives
the shell over a serial socket. It assembles its own ESP from the small
checkpoint, so it never disturbs deploy staging. It prefers the release
artifact, so a debug build alone leaves a stale binary staged and the change
under test never boots.
Pass --qemu-extra "-accel kvm -cpu host" on Linux, or "-accel whpx -cpu max"
on Windows. A hypervisor accelerator is about 160 times faster than TCG on this
workload, and the CPU model alongside it is what exposes AVX2, without which the
trainer declines. It also raises
unmasked SSE exceptions faithfully where TCG does not, which is how a real bug
in per-task FPU initialisation was found.
Pass initiative off and then agent stop as the first commands: the resident
task wakes fifteen seconds in and holds the engine for a whole episode, and
stopping future ticks does not cancel the one already in flight.
The large checkpoints do run here. The synthetic FAT path is FAT16 on a
fixed geometry with a 516 MB ceiling, so it cannot stage one. --stage-iso
has no such cap: it builds a one-shot bootable image and boots that instead,
which is how any large checkpoint reaches emulation. Saying these models were
runnable only on the laptop was wrong on both halves, because the image path
existed and the hypervisor made it fast enough to matter.
Testing the staged-update path needs a real disk rather than the synthetic one,
which cannot do directory operations at all. tools/mkesp.py builds a raw
FAT32 image with an MBR, and --esp-image reuses it across boots so what the
guest wrote is still there next time.
.\scripts\deploy.ps1 -EspDrive S: -ReleaseThen reboot and hold F11 for the boot menu.
The boot image is replaced by the next boot rather than the running one, because the firmware's FAT driver is the only writer of the ESP that exists while a boot image can still be swapped. Put three files on the ESP and reboot:
GLADOS/STAGED.EFI the new image
GLADOS/STAGED.SIG its detached GLADOSIG signature
GLADOS/UPDATE.FLG any contents; presence is the request
A key is provisioned and the updater is live. This said UPDATE_KEY was
all zeroes for a while after it stopped being true, and the cost was real: it
sent a session looking for why the update channel could not publish, on the day
it published. UPDATE_KEY in src/update/mod.rs holds a real P-256 point, the
release workflow signs and uploads, and update check, update fetch and
update stage are separate verbs because claiming a write range on the boot
partition is the most dangerous thing this system does.
To rotate: tools/sign.py --keygen --out FILE, paste the public rows into
src/update/mod.rs, and rebuild -- adopting a signer is itself a kernel change,
which is the point. Use --out, because without it the private half goes
to stdout, which is exactly how the last one died. The first build carrying a
new key cannot be delivered by this system, since no kernel in the field trusts
it yet; that one ships as an ISO.
The rollback copy is taken and read back before anything is overwritten, the health flag is cleared before the window rather than after, the written image is verified by digest, and a mismatch puts the old image straight back. The decision function is pure, so all eight of its states are asserted at boot without staging anything.
python tools/mkiso.py glados.iso \
--efi target/x86_64-unknown-uefi/release/glados.efi \
--payload esp/GLADOSmkiso.py writes a FAT32 EFI System Partition and wraps it in ISO 9660 with an
El Torito EFI boot entry. Both formats are built from scratch, since xorriso
and oscdimg are absent from most Windows machines and neither format is large
enough to justify the dependency.
It generates VFAT long-name entries, which is not optional: the kernel opens
\GLADOS\tokenizer.bin, whose base name is nine characters and cannot be
expressed as 8.3. A short-name-only image presents it as TOKENI~1.BIN, and
the kernel then fails to find its tokenizer at boot, on real hardware, with no
filesystem left to debug from.
Test the image the same way anything else here is tested:
python tools/drive.py --iso glados.iso "ls /"There is no cargo test. This is a no_std UEFI binary with no host test
runner, so verification is the boot selftests plus driving QEMU.
At boot the system runs about thirty selftest sections -- count the
[selftest] headings in a boot log, the only figure that cannot go stale --
printing ok or FAIL per line: heap, timer, clock, the namespace's Merkle addressing, fifteen sets
of published cipher vectors, fault handling, running machine code from the
heap, file type detection, the glyph table and the UTF-8 decoder, constrained
decoding, the agent loop, the linear probe, the situation planner, the
initiative policy, the self-modification gate, corpus bundles, QDoRA adapters,
the backward kernels, and the trainer's arithmetic.
A hundred and two suites can be re-run on demand with diag all or diag <name>.
Registration is deliberately awkward: SLOTS is one number, and a suite added
without a slot in the results table fails a compile-time assertion rather than
silently never recording a verdict. That guard has earned its place -- it once
compared SUITES.len() against a literal while the array's length was a
separate literal beside it, so the thirty-third suite passed the guard and then
panicked at the store, which is precisely the failure the guard's own comment
says it prevents.
Both of those numbers were wrong here for several releases, and in different directions from the ones in the project's own notes. They are counted now rather than remembered:
python tools/drive.py "initiative off" "agent stop" "diag" # then count
grep -n "const SLOTS" src/diag.rs # 102, assertedA bare diag is not a clean sweep. It prints the table with - beside
everything that has not run and a tally reading 0 passed, 0 failed, 102 not run, which is easy to read as the opposite of what it is. diag all runs
them.
That output is the test suite. It is easy to scroll past and it does catch
real bugs. An ECDSA break sat visible in [selftest] crypto for an entire
debugging cycle while the log was being sliced down to look at something else,
and a failing sparsity claim shipped for one commit the same way.
diag differ is the newest instrument and the one with the most opinion in it.
It runs one program two ways and requires agreement on value, step count and
error text, bit for bit, sixty-four times over. It also runs a pair that is
supposed to disagree and fails if that is not caught, because a harness which
has never reported a difference is indistinguishable from one that compares
nothing.
tools/reference.py is a NumPy oracle for the dense models and tools/v4.py
for the hybrids. Both read the converted file, so a convert.py bug shows up
there too and only a Rust bug shows up as a mismatch.
A few decisions that are load-bearing and non-obvious.
Constrained decoding makes invalid output unreachable. The grammar is built from the live applet table, and read-only mode works by removing mutating applets from the reachable set before sampling, so no sequence of sampling outcomes names a tool that does not exist. Permission enforcement and output validity end up being the same piece of code, which leaves nowhere for the two to disagree.
The closed-form router beats the transformer. Two paths exist: decoding an applet name token-by-token under a grammar, and reading one hidden state into a closed-form ridge regression (Widrow-Hoff, 1960) solved by Cholesky in-kernel. The regression is 12,672 parameters, runs in about 1.6 ms with no transformer forward pass, and scores 54.7% on the held-out split against 32.1% for nearest neighbour and 5.7% for the 135M model asked the same question.
Agreement carries more information than the vote. Three cores classify independently. Combining them into a single better answer was measured and does not work, scoring 76.9% against 77.8% for the best core alone. What their agreement predicts is correctness: 90.3% right where all three pick the same applet, 50% where they split. That gap is what the gate acts on, and a router that knows when it is guessing is worth more than the point the ensemble was supposed to buy.
Freezing the base is what makes training affordable. A frozen weight means a hidden state is a constant, a constant can be cached, and a cached decision can be replayed against any number of candidate adapters for the price of a dot product. Every capability above rests on that one property, including the judging: it is why a verdict this machine reaches at three in the morning can be re-checked by anybody, later, for almost nothing. Anything that proposes to train the attention path is proposing to give this up, which is a real trade and worth naming before it is made.
The content hash covers content only, never block locations. Otherwise moving a block would rename an object.
NVMe writes are locked by default, and the unlock names a range. It was one bit
for a long time: unlocking said writes were allowed and nothing said where, so
from the moment initialisation succeeded every LBA on the device was writable,
including the partition table and the Windows volume that is still the only
other thing on this disk. The window is enforced on every write and printed
beside the word UNLOCKED, and diag wgate asserts the whole decision without a
device and without writing anything.
A model can be wrong without being broken. RoPE pairing, QK-Norm, head width, RMSNorm epsilon and the pre-tokenizer regex each produce a network that loads, runs, stays numerically well-behaved, and writes fluent text. There is no error to catch. The kernel used the wrong RoPE convention for a long time and nothing looked broken, because both conventions are norm-preserving rotations by the same angles; the model stayed fluent and attended by a scrambled notion of distance, which is indistinguishable from the outside from a small model being small.
Negative results stay in the tree. Training the SGD head hurts at this data scale -- 30% held-out untrained, 10% after two epochs, 0% after eight, because forty examples across twenty-one classes leave gradient descent nothing to do but memorise. That is the head the ridge probe replaced, and it is not the QDoRA adapter; the two have nothing to do with each other. This sentence said "the adapter head" for a long time and it cost a session, which is the whole argument for naming the thing precisely in the summary and not only in the file it belongs to. The Product-of-Experts council does not improve accuracy either. A renderer survey ranked volatile framebuffer writes as the single largest constant-factor loss and the measurement disagreed; the real win was that blank console cells were being painted one pixel at a time under a background that had just been filled, which took a frame from 2,376 us to 1,629 us. All of them are kept, because the reason to know them is the reason they were worth measuring, and because a deleted experiment gets repeated.
The measurements here are small, and they say so where they appear. The adapter trainer has been exercised on subsamples of a few dozen decisions, which establishes that the machinery composes and establishes nothing about how much it helps.
For a long time a full-corpus pass was believed to need the laptop, because a forward-pass group took 286 s under emulation. It took 1.8 s the first time anybody tried the hypervisor accelerator instead of the interpreter, and the whole corpus is about twenty minutes. The belief was never measured, and it shaped which questions seemed answerable.
Copyright © 2026. All rights reserved.
No licence is granted. This source is published to be read and not to be reused. You may not copy, modify, redistribute or create derivative works from it without written permission.
The reservation above covers this project's own work. NOTICE.md lists
everything else and its licence: the vendored font crates (MIT, Apache-2.0,
Zlib), the two typefaces (OFL), and the Intel register layouts in
src/dev/iwx/, used under the BSD arm of Intel's dual licence. The images
carry Intel's Wi-Fi firmware unmodified, with LICENCE.iwlwifi_firmware beside
it. The GPL-2.0
Realtek tables this section used to describe are gone, with the driver that
needed them.
Model weights are not distributed in this repository. The ISO includes a converted Qwen3-0.6B (Apache-2.0, Alibaba Cloud), Kokoro-82M with misaki's lexicon (Apache-2.0), and the FlyWire connectome (CC BY 4.0, FlyWire Consortium, with signs from Eckstein & Bates 2024). Each licence travels with its file.
GLaDOS, Aperture Science and Portal are properties of Valve Corporation. This project is independent and is not affiliated with, endorsed by, or connected to Valve in any way.
That line used to say "an independent, non-commercial homage". The project has a token attached to it, which makes the non-commercial clause untrue, and a disclaimer carrying a false clause is worse than a shorter one. What was doing the work is the rest of the sentence.



