Skip to content

Releases: IlumCI/GLaDOS

v1.2.28

Choose a tag to compare

@IlumCI IlumCI released this 31 Aug 19:21

An in-OS updater. Notes to follow.

GLaDOS 1.2.27

Choose a tag to compare

@IlumCI IlumCI released this 31 Aug 14:35

The release that reads its own battery.

Reading a charge on a laptop means running the bytecode the firmware ships in its DSDT. There is no shortcut and no register to poke. So this release is mostly the interpreter that does it, and the battery is what it was built for.

The ACPI interpreter

  • Table discovery that keeps what it finds and checksums all of it. The DSDT is the one table the root list does not point at, since it hangs off the FADT at offset 40, or offset 140 on a machine whose tables sit above 4 GiB. Walking the root list never found it, which is why this kernel had never printed its address.
  • A namespace built from the AML, walked to the last byte. That assertion is the whole test: package lengths live inside the stream, so one misread length does not lose one object, it desynchronises everything after it, and a parser that is ninety per cent right produces a complete-looking namespace full of names the firmware never wrote.
  • An evaluator with the opcode set real methods contain. It runs only what a caller names, and an opcode with no arm returns an error carrying that opcode and its offset, so the next machine that needs something costs one line rather than an investigation.
  • Operation regions in system memory, system I/O and the embedded controller, with fields read at their declared bit offsets and widths.
  • src/dev/ec.rs, the embedded controller: a command port, a data port and two status bits.

The parser had to be exact and the evaluator was allowed to be partial. Those are opposite obligations, and separating them is what turned an open-ended job into a bounded one.

The walk never enters a method body. Everything that declares a name is package-delimited, so bodies are stepped over by length, and that avoids the one genuinely hard problem in AML parsing: a bare name followed by arguments is a call whose argument count depends on a declaration that may live in a table not yet loaded. ACPICA solves it with multiple passes. By the time the evaluator meets it, the namespace is complete and the arity is simply known.

Three bounds, because this is firmware bytecode running in ring 0 where a fault outside a guard is fatal. A step budget, since While (One) {} is legal AML and vendor methods contain loops that wait on hardware which may not be there. A depth cap, since a method may call itself and there is no guard page under this stack. And nothing runs unasked: building the namespace executes nothing at all, which is why a top-level Store is stepped over rather than executed even though ACPI says it should run at table load.

Region writes are off until acpi unlock, the same shape as store unlock and fat unlock. Reading a battery needs none, and a stray write to an embedded controller is not a wrong number: it is a fan that stops or a charge threshold that moves, on hardware, permanently.

The battery

battery reports charge, state, time remaining, capacity, draw and health. There is a Power page in Settings, a percentage in its own well on the taskbar, a Battery record in the language so the model can ask, and the charge and the temperature both joined the situation the machine keeps about itself.

The trap is that the unit is a field of the thing it measures. _BIF element zero says whether the whole set is milliwatts or milliamps, and machines differ, so a capacity in mAh over a rate in mW gives a number that looks like a time and is wrong by the battery's voltage. Everything converts once at the boundary. The percentage is computed before any conversion, since remaining and last-full always share a unit with each other and converting first would round twice to answer a question that needed no conversion.

0xFFFFFFFF is ACPI's "unknown" and is refused rather than believed, which is how a battery comes to report a capacity of four billion.

Three states are kept apart that are tempting to collapse: the device existing, a cell being in the bay, and the cell answering. A machine with no battery and a machine whose battery cannot be read are different, and an operator needs to know which.

Power, and turning off

  • power::tick finally has a caller. It was written to hold the governor down when the part gets hot, and nothing had ever called it, so the thermal policy had been dormant since the day it was committed.
  • The governor holds at powersave on battery and releases on mains. Each policy tracks its own hold, because both can want the governor down and only one may release it: a machine that was hot and unplugged must not return to performance the moment it cools while still on battery. Both announce themselves, since a machine that quietly halves its own clock is indistinguishable from one that is broken.
  • The nightly self-modification loop will not run on battery. It already required the small hours and no hardware input. Two passes over the corpus for a deep trial is the most expensive thing this machine does, and a laptop that spends the night improving itself into a flat battery has not improved itself.
  • Real ACPI power-off. src/cpu/mod.rs carried a comment saying that doing this by hand meant parsing the DSDT for \_S5 and writing PM1a and PM1b, an interpreter's worth of work for something the firmware already does. That was a fair trade until the interpreter existed for the battery. The firmware is still asked first; ACPI is the second chance, and "hold the button" is no longer the answer when it declines. There is no standard sleep value: \_S5 is a package the board keeps in its own namespace, which is exactly why this needed an interpreter and not a constant.

USB keyboards and mice

On the HID boot protocol, over xHCI. Enumeration, SET_PROTOCOL, an interrupt endpoint, and reports diffed against the previous one to turn state into events.

The driver took an afternoon and the bug underneath it took the rest of the day. Address Device was refused while every field this code wrote was correct: contexts aligned, slot context right, input control context reading drop 0 add 3. QEMU's own trace said what six rounds of re-reading our side could not. The first Address Device succeeds; a controller will not address a second slot to a port that already has one, and enumerate had no counterpart, so the network probe walked every port at boot looking for an adapter, kept none of them, and left every port owned by a dead slot. USB enumeration had been single-shot per boot since the day that probe was written.

A HID keyboard reports usage codes and this turns them into PS/2 scancodes for the existing decoder rather than into characters, because shift, caps lock, Alt held against Alt tapped and Alt-Tab are policy, and policy written twice disagrees with itself.

Fixed

  • Four opcodes the namespace walk could not step over, each found by printing bytes rather than reasoning about them: a region offset given as a name, one computed with Add, a bare top-level Store, and the CreateWordField family. Nodes went 70, 2491, 3367, 5110, 5889.
  • The hex dump that explains a parse failure indexed the machine's own tables while the offset came from a loaded blob, read a 575 KB offset into a 9 KB slice, and panicked. A diagnostic whose job is to explain a failure became one.
  • diag migrate sampled a single 200 ms window and called task migration broken if no second core had picked the task up inside it. It reported a failure on a boot that had just allocated a 768 MiB cache, and four consecutive runs afterwards saw the task on two, three, four and four cores.
  • A field list reuses the package-length encoding to carry a count of bits rather than a byte range. Reading it as a range makes a one-byte field report zero width.
  • The cycle bit in a transfer ring is now written last and on its own. A TRB belongs to the controller the instant that bit matches, so a single struct-sized store let it see a request whose parameter had not been written yet.

Images

Six bootable UEFI ISOs, checksums in SHA256SUMS. All boot to 23 passed, 0 failed.

Image Model Context KV cache Size
glados-1.2.27.iso Qwen3-0.6B 512 112 MiB 576 MB
glados-1.2.27-qwen35-2b.iso Qwen3.5-2B hybrid 512 12 MiB 1810 MB
glados-1.2.27-qwen35-2b-8k.iso Qwen3.5-2B hybrid 8192 192 MiB 1810 MB
glados-1.2.27-qwen35-2b-32k.iso Qwen3.5-2B hybrid 32768 768 MiB 1810 MB
glados-1.2.27-smollm2-135m.iso SmolLM2-135M 512 112 MiB 133 MB
glados-1.2.27-nomodel.iso none 33 MB

The 2B images want 5 GB of RAM, and 8 GB for the 32k. The 135M image is the one to reach for under emulation.

Known

  • The embedded controller's success path has never run. QEMU models no controller: the ports answer 0xFF, both status bits look set forever, and only the timeout is proven. Every battery figure seen under emulation is the firmware's fallback branch rather than a reading, so a percentage shown in a virtual machine is not a measurement.
  • Index fields and bank fields are refused rather than read, since both need writes and writes are off.
  • Top-level conditional blocks are skipped whole and counted. Names inside them are not defined. QEMU has one; a real laptop had 142, all _OSI checks.
  • The update key is all zeroes, so staged updates verify as NoKey and are refused until a signer is provisioned.
  • The console takes a #GP if it paints from inside an interrupt gate. Older than the fault reporter, and visible rather than silent.
  • An authored application is still left as a draft and never adopted.

Licensing

The kernel source is not licensed for reuse. The model weights are Apache-2.0 and their licence travels in each image at \GLADOS\LICENSE.TXT.

GLaDOS 1.2.26

Choose a tag to compare

@IlumCI IlumCI released this 31 Aug 09:18

The release where the machine changes itself.

134 commits since 1.1.0. The agent loop, the self-modification loop and the ability to write its own applications all existed as sketches then and exist as working paths now, each with judges in front of them. Everything below is checked by running it, and the boot output is the test suite.

The machine changes itself

  • godel is the loop that lets the kernel modify itself, and src/ai/godel.rs opens with why it departs from Schmidhuber's construction: there is no theorem prover here, so proof is replaced by a certificate cheaper to refute than to produce, over content-addressed inputs, re-derivable bit for bit by any later run.
  • Four judges, unanimity required, each a different failure mode. J1 is a paired McNemar test over the same cached decisions both variants answer, which is what a comparison of two percentages cannot be. J2 replays the machine's own curiosity goals along the frozen baseline's path. J3 is structural: finite factors, positive scales, finite logits. J4 is cost, in rank and resident bytes.
  • Adoption is a pointer swap, so godel rollback costs a pointer write, and /ai/godel/ledger.txt gets a line per trial either way.
  • One loop for any kind of change. A proposal is an adapter grid point, a routing rule, a deep training run, a skill or a council core, and the same judges decide all of them.
  • The night branch rotates over every axis that has a judge. It knew two jobs and godel always won the tie, so the adapter grid was walked to exhaustion while the routing rule, deep training and core composition were never tried unattended at all. Search space exhausted was the end of self-improvement: eight points and then nothing, every night forever. godel next now reports where the rotation stands without spending the night finding out.
  • There is no random seed, and that was a bug rather than a virtue. Every trial trained a bit-identical adapter with the same content hash, so after the first adoption each later one was compared against itself: nothing repaired, nothing broken, rejected, forever. The fix is a declared grid walked in a fixed order with tried points marked, so the next point is a function of the ledger rather than a coin.
  • The test slice carries a budget. It is consulted only after a variant has won on validation, the ledger counts the reads, and past three a test figure prints as stale and unquotable. A machine that improves itself every night reads the held-out set every night, and this project's measurement discipline does not survive that unless somebody counts.
  • The routing rule is searchable at last, judged on calibration rather than accuracy, because what a rule moves is how much better the council's confident answers are than its unconfident ones. Requiring a win on J1 is exactly what made the axis unsearchable; the floor is symmetric now.
  • A council core is a proposal, not just an operator command. core trial writes a node, a ledger line and something rollback can undo, and rollback restores the core as well, because rolling back an adopted core used to leave it installed and voting.

The agent, and applications it writes

  • Agent episodes run applet calls in a loop, capture observations, write a transcript to /ai/episodes, and freeze successes into skills.
  • Three-tier decisions: reflex dispatches on router-and-council agreement before the sampler runs, pulse takes one greedy constrained walk, deliberation forks sampled candidates and ranks them with the router.
  • A skill is judged before it becomes a tool. agent learn used to compile a successful episode into /ai/tools and that was the whole of adoption. Four judges now decide, constrained by what a replay skill actually is: it parses, it runs under the powers an unadopted skill really has, it repeats, and it is cheap.
  • A skill is not automatically the operator. Every program under /ai/tools used to run with operator capabilities including raw memory, I/O ports, the network and the framebuffer. It was open by omission rather than by argument. Identity is the hash of the file, so editing a trusted skill revokes its trust by construction.
  • run's argument is decoded under a grammar built from the live skill list. The applet name always was, but the argument was free text, so the model had to spell /ai/tools/learned-3f2a91c4.ai&xi exactly: adoption that put a tool in the toolkit nothing could pick up.
  • Applications are documents and programs at once. Manifests, lineage, and a grant that names one artifact rather than a capability class.
  • The machine writes applications. Skeletons and drafts came first with no model involved at all, then the authoring loop, then the model driving it. Skeletons emit records and types, and the machine writes one unattended.
  • A progress window shows step N of M, clauses met and the last verdict verbatim. No progress bar and no estimate, because a step can be a skeleton that lands instantly or a decode that takes seconds.
  • The engine has one holder. HOLDER records the task and with_engine claims it for the length of a call, replacing a flag-and-id pair per task, which is why a third holder was invisible: the nightly trial ran on the initiative task, set neither flag, and handed a second &mut Engine to anyone who asked for twenty seconds.
  • One queue, one busy flag, one abort, one task for episodes and authoring both, because a second task running a second kind of work needs a second entry in the engine's exclusion check.
  • A resident mind thinks between commands, and aixi-lite plans over the machine's own fitted dynamics with Monte Carlo rollouts.

The language

  • The system language is called Aiksi, and its capability gate was inverted from a denylist to an allowlist. A denylist grants by default, so the builtin anyone forgets is the one that matters. Every row is name, touch class and arity, and an arm without a row is dead code rather than an ungated builtin.
  • It reaches the kernel: text, integer arithmetic, lists, the namespace, the clock and counters, tasks and memory, PCI, network status, sockets, the model, the framebuffer, raw memory and I/O ports.
  • Records, optional types and use. A record is a declaration and a constructor in one, records are values so nothing has to explain aliasing, and types are checked where a value crosses a boundary somebody annotated rather than inferred by a solver.
  • Everything that is actually a struct answers a record now. pci_list gives Device, rtc_now gives Time, net_ifaces gives Iface. It answered text only because there was nowhere to put a field, and every caller then wrote the same fragile split to take it apart.
  • A native code generator: src/aiksi/jit.rs compiles integer arithmetic, if, while and return to x86-64, emits into a page-aligned heap buffer and calls it through a pinned sysv64 pointer. Anything outside that slice is refused rather than half-compiled.
  • diag differ runs one program two ways and requires agreement on value, step count and error text, bit for bit, over 64 rounds. It carries a canary case that must fail, because a harness that has never reported a difference is indistinguishable from one that compares nothing.
  • A step was timed for the first time in this project's history, and the measurement refuted the plan that asked for it. The tree-walk was never the cost. Three rounds of removing allocations took a vote from about 418 step-equivalents to 146, and none of it came from compiling anything.

Training

  • QDoRA adapters over the classifier and over every q/k/v site, with the backward kernels to match: activation adjoints for RMSNorm, SwiGLU, attention and RoPE, then full site gradients including the norm terms.
  • Tape keeps the residual stream entering each layer and nothing else; Model::backward walks layers last to first and recomputes each from that stream.
  • The input gradient, which Dora::backward never produced, and which was the single absence that made a classifier adapter trainable and a q/k/v adapter not.
  • An exact f32 KV cache for training, because the served int8 cache makes the loss piecewise constant in anything upstream of a cached key: differencing a layer-0 query through the quantised forward reported -0.305 against an analytic gradient of order 1e-6.
  • The gradient check is directional and uses cross-entropy, both for resolution. A per-entry difference asks f32 to resolve a float quantum wearing the costume of a derivative.
  • Adam and restricted cross-entropy, proven by overfit, with the gradient zeroed outside the grammar's candidate set so only rows the decoder can reach ever move: 132 rows out of 49,152 on the measured decision layer.
  • Deep training is judged now. It was the one training path with nothing in front of it, walking gradients into the live model and keeping whatever came out.

The conversation

  • One conversation, continued, that survives a reboot. It resumes the KV cache instead of re-sending a transcript, so the tenth exchange costs the tokens of the tenth exchange.
  • It outlives its context. Within 64 positions of the trained length the cache becomes a ring with four sinks and evicts its oldest turns instead of stopping.
  • The system turn is pinned, using the same sink mechanism, so the instructions and the applet list keep their original slots and RoPE angles for as long as the conversation runs.
  • Autosnap is on by default and the KV context is deliberately not in it. Parking the cache per turn wrote 16,375 blocks and took half a 27 MiB region in two turns; removing it took the same measurement to one block.

More than one core

  • Application processors start, walk up to long mode and run work. Mat::matvec and the backward adjoint are both split across every core, the adjoint by column because it accumulates down rows.
  • The heap and the console are behind a real spinlock with lock_irq, because allocation happens under interrupts and the cloc...
Read more

V1.1.0

Choose a tag to compare

@IlumCI IlumCI released this 25 Aug 18:20

The deliberation release.

New

  • Three-tier agent decisions: reflex dispatches on router-and-council agreement before the sampler runs, pulse takes one greedy constrained walk, deliberation forks sampled candidates and ranks them with the router.
  • Agent episodes: agent <goal> [-n steps] [--trust full] runs applet calls in a loop, captures observations, writes a transcript to /ai/episodes, and freezes successes into skills with agent learn <name>.
  • Skills call the OS: the language interpreter gained a nesting-safe applet("ls /sys") builtin, so learned programs compose OS calls.
  • Qwen3.5 hybrid support end to end: linear-attention layers beside gated full attention, walk-checked v4 checkpoints, and the new 1.8 GB Qwen3.5-2B flagship image (route 40%, MMLU 43.3% host-side).
  • Weight-stationary prefill: prompts stream each weight matrix once per 64-row chunk instead of once per token, bit-identical results, episode steps under emulation down from over 35 minutes to about 2.

Fixed

  • Serial input stalls under QEMU: the 16550 receive FIFO held short commands below its trigger level; FIFO init is now off.
  • Constrained walks resumed from a stale position marker and rewrote prompt keys; position now travels with the cache everywhere.

Images

Four bootable UEFI ISOs, checksums in SHA256SUMS. glados-qwen35-2b.iso wants roughly 8 GB of RAM. glados-smollm2-135m.iso fits QEMU development at half of its previous size after an ISO writer fix.

V1.0.0

Choose a tag to compare

@IlumCI IlumCI released this 14 Aug 02:03

Initial release

  • Working GUI
  • Working model
  • Testing Networking

Full Changelog: https://github.com/IlumCI/GLaDOS/commits/MajorVersion