> e1z0@kamarovas.net

~/thoughts $ less anotheros-intro.md

AnotherOS: Because Apparently One Operating System Was Not Enough

2026-07-31

prj anotheros logo

How a nostalgic DOS-clone experiment, a pile of aging laptops, too many language models, and a complete disregard for sensible summer hobbies turned into a real Unix-like operating system.

It started in 2003, on hardware that was already old

Back in 2003, I tried to build my own operating system.

The target was a very old 80386SX, and the grand architectural vision was essentially: “What if DOS, but mine?” Some parts worked. Some parts absolutely did not. That is the diplomatic description. The less diplomatic description is that I learned a lot about boot sectors, real mode, hardware limitations, and the special kind of optimism required to stare at a frozen screen for six hours and still call the day productive.

Nevertheless, the project stayed with me. It became one of those nostalgic ideas that never quite disappears. You do not actively work on it, but every few years it taps you on the shoulder and whispers, “Remember when we thought writing an operating system was a reasonable weekend activity?”

For years I kept telling myself that I would return to it someday. Not to build a full commercial operating system. I had no plan to compete with Linux, Windows, macOS, BSD, or the firmware inside a microwave oven. I simply wanted to build something that booted, did useful work, and satisfied the part of my brain that becomes dangerously energetic when told that a project is unnecessary.

Why did I need it?

I did not.

Why did I want it?

Because I could try.

That is often enough to start a very bad idea. Occasionally it is also enough to start a very good project.

I am not an operating-system expert, which is a wonderful way to begin

I am not going to pretend that I am an operating-system design authority. People who know me are more likely to remember me as a reverse engineer, Unix administrator, developer, or the person who voluntarily keeps too much obsolete hardware alive.

But operating systems are not completely foreign territory to me. I worked on projects such as .NET Linux, a Gentoo-based distribution, and UbuntuBSD, which combined a BSD kernel with GNU utilities and a Debian-style userland. I have spent enough years around kernels, bootloaders, toolchains, filesystems, drivers, networks, build systems, and broken computers to understand two important facts:

  1. I know roughly how the pieces fit together.
  2. Building even a partially useful operating system normally takes an absurd amount of time.

Getting a kernel to print text in QEMU is a hobby project. Getting an operating system to boot on real hardware, use multiple processors, mount filesystems, talk to a network, draw a desktop, and run existing applications is closer to entering the Olympics after training in your garage.

Look at projects such as Syllable, Haiku, SkyOS, MenuetOS, SerenityOS, and many others. Their developers invested years—often decades—into making something that boots reliably and performs real work. Haiku is perhaps the clearest example of what persistence can produce. Its community continues to improve the system, port software, and write drivers. More recently, developers have also begun using large language models to accelerate some of that work.

That last part interested me.

A year of testing artificial colleagues

For more than a year, I have actively tested ChatGPT, Claude, Kimi, Grok, and other proprietary models. I have also run local models through tools such as llama.cpp and Ollama.

This research has consumed a respectable amount of money and a less respectable amount of sleep. It has also taught me that artificial intelligence is neither magic nor useless. It is a multiplier. Unfortunately, multiplication works on bad numbers too.

Give an agent a clear task, good context, a test environment, and a way to verify its work, and it can be astonishingly productive. Give it a vague wish, a corrupted build tree, and three hundred contradictory Markdown files, and it will confidently build a staircase into the ceiling.

I am getting older at a speed I consider technically unnecessary. Technology moves faster every year, my available time moves in the opposite direction, and I also have a family business that requires real work in the real world. I have built a homelab with enough agents and hardware to make poor life choices at scale, but this story is not about the rack, the electricity bill, or why one person apparently needs several machines to argue with several chatbots at once.

This story is about what happened when I aimed that setup at an operating system.

About prompt engineering, courses, and bags full of air

At the moment, everybody is “vibe coding.” Some people are just starting. Others are producing complete websites and applications by describing what they want and repeatedly pressing Enter until the build turns green.

I wanted to learn how far the same approach could go with systems programming.

I also wanted to learn how to write better prompts, although I remain suspicious of the industry that has appeared around teaching people to do it. In my opinion, much of prompt-engineering culture resembles the worst period of search-engine optimisation consulting: complicated terminology, impressive diagrams, expensive courses, and a promise that the results will appear later because Google—or the universe—has not indexed your genius yet.

You do not need a sacred incantation.

You need to explain what you want, define the constraints, provide the relevant context, and specify how success will be tested. Then you observe what fails, improve the instructions, and try again. It is closer to managing an enthusiastic junior developer than casting a spell.

Experiment. Notice which instructions produce better results. Separate large tasks into smaller ones. Demand evidence. Preserve working states. Make backups. Do not allow eight agents to “improve” the same header file simultaneously unless you enjoy archaeological debugging.

It is easier than assembling some LEGO sets, although LEGO pieces hallucinate less frequently.

The most sensible summer activity: writing an OS with LLMs

While normal people were spending the summer travelling, resting, swimming, or doing anything that did not involve interrupt controllers, I decided that the most meaningful possible seasonal activity was to build an operating system with the help of language-model agents.

To be fair, I was also working very hard on our family business. That meant the operating-system work happened around everything else: late at night, early in the morning, during gaps in the schedule, and occasionally at times when a responsible adult would have gone to sleep.

The idea itself was not entirely new. AI-generated hobby systems already existed, including projects such as SlopOS and various smaller experiments. Many were entertaining demonstrations, but I wanted something more complete. I did not merely want a kernel that printed a message, ran one hard-coded process, and declared victory before the first page fault.

I wanted my system to run on real hardware and perform the essential work expected of a Unix-like machine.

The target was ambitious:

  • a Unix-like and substantially POSIX-capable environment;
  • symmetric multiprocessing;
  • real multithreading across all available cores;
  • no permanent global big kernel lock;
  • a flexible architecture;
  • useful networking and storage;
  • a desktop environment;
  • enough compatibility to port substantial existing software;
  • an MIT-licensed original implementation, with compatible interfaces rather than copied GPL kernel code.

I was not trying to invent every interface again. Reinventing read() under a new name is not innovation; it is a way to make every port unnecessarily painful. The goal was to implement familiar Unix and Linux-facing interfaces cleanly, while keeping the kernel implementation original and permissively licensed.

The project became AnotherOS.

The first 24 hours: from nothing to a shell

The initial plan was modest. We divided development into stages, with stage eight defined as a fully bootable system reaching a shell prompt.

The early userland used a minimal C library implementation, without luxuries such as full terminfo support. The immediate objective was simply to reach a basic ash shell and prove that the complete path worked: bootloader, kernel initialisation, process creation, system calls, executable loading, terminal I/O, and enough libc functionality for the shell to remain alive.

After fixes, retries, broken builds, and the usual ritual sacrifice of perfectly reasonable assumptions, the system reached that point in roughly 24 hours.

That should have been a satisfying milestone.

Instead, it triggered greed.

There is an old saying that appetite grows with eating. Apparently it also grows when your operating system prints a prompt. Once the shell appeared, I immediately stopped caring that the original goal had been achieved. I wanted a proper libc. I wanted real software. I wanted networking. I wanted graphics. I wanted all of it, preferably yesterday.

I burned through Claude Max allowances with impressive efficiency. Its multi-agent tooling—especially configurations using eight to ten agents—could be extremely productive, but only when properly orchestrated. Different agents had to own different parts of the system. If several started editing the same files, the result could include duplicated code, missing code, incompatible assumptions, and the occasional reconstruction of a subsystem that had already been finished.

An AI agent is very fast. So is a chainsaw. Organisation determines whether you obtain furniture or lose a wall.

From a minimal libc to musl, BusyBox, and X

A toy libc was enough to reach a shell, but it was not enough for the system I wanted. I chose musl as the real C library.

AnotherOS is written mostly in freestanding C, with assembly used for the places where the processor refuses to accept a polite high-level suggestion. The system exposes the Linux/i386 int 0x80 system-call ABI. The interface is compatible; the implementation underneath it is original.

prj anotheros fbterm first

That decision paid off quickly. It created a practical path for running existing software without pretending the operating system was Linux internally.

Within the next four days, the project gained:

  • a statically linked musl userland;
  • BusyBox;
  • a complete X.Org stack running on the framebuffer;
  • early 2D acceleration work;
  • a LinuxKPI compatibility layer intended to support ported graphics drivers;
  • Intel and Radeon graphics work;
  • IceWM, branded with an AnotherOS start button.

At this point, the operating system had already travelled a suspiciously long distance from “small experiment.”

The framebuffer became fast enough that X felt genuinely responsive, particularly once 2D acceleration paths began working on Intel and Radeon hardware. IceWM provided a practical desktop and an immediate way to run graphical programs.

prj anotheros direct framebuffer

Naturally, I looked at this functioning graphical environment and decided it was still not enough.

prj anotheros wsys

A package manager that survives the apocalypse

I wanted a cleaner filesystem layout, inspired by systems such as Apple platforms and GoboLinux. Traditional Unix directory trees work, but they can become a historical sediment layer of binaries, libraries, configuration files, package-manager metadata, compatibility symlinks, and decisions made by people who assumed disks would remain smaller than a modern photograph.

AnotherOS adopted a GoboLinux-derived package layout:

/Apps/<Name>/<Version>
/System/Index

Each package lives in its own versioned directory. A symlink farm exposes the active version through /System/Index. Switching versions means relinking Current; rolling back is effectively an O(1) operation.

The package manager, apkg, was written in C without depending on external archive utilities. It contains its own SHA-256 implementation, ustar reader, DEFLATE decompressor, and HTTP client.

Later, the rest of the system moved to dynamic linking, but apkg deliberately remained static. If the dynamic linker is broken, shared libraries are missing, and userland is lying on the floor making unhealthy noises, the package manager should still work. Depending on eight BusyBox applets to repair the package system would be an impressive example of circular optimism.

X was useful, but I wanted a façade

X11 worked. GTK3 and Qt6 worked. WebKitGTK and a MiniBrowser followed. That was already a major achievement.

But X is old, large, historically layered, and not the desktop architecture I wanted as the final answer. I wanted a fresh graphical stack: something visually closer to macOS, functionally coherent, and under the project's control from the framebuffer to the application protocol.

prj anotheros x go miqt qt6

prj anotheros x gtk3

prj anotheros x cairo

prj anotheros x webkitgtk

So I asked the agents to build a new desktop stack inspired by macOS, but with AnotherOS branding and its own implementation.

The result is called Fasadas—Lithuanian for “façade.” The name seemed appropriate for the part of the system whose job is to make everything else look civilised.

Fasadas is not an X11 theme. It is a from-scratch compositor, window manager, graphics layer, desktop shell, application-bundle format, and set of toolkit backends.

prj anotheros fasadas

Its central process, fasadasd, owns /dev/fb0, /dev/mouse, and /dev/kbd. Clients communicate through a framed protocol over an AF_UNIX socket. ARGB surfaces are shared through zero-copy memfd objects passed using SCM_RIGHTS. Where a graphics driver provides the necessary support, the compositor performs tear-free, vblank-latched double-buffered flips.

The window chrome, translucent global menu bar, system tray, cursor, shadows, blur, and frosted-glass effects are drawn through Kvarcas, a Core Graphics-style 2D library layered over Cairo and Pango.

The dock is called Plintusas. It provides a glass shelf, perspective, mirrored reflections, running-application indicators, cosine-squared pointer magnification, and a genie-style minimise animation that sends a window back to its application icon.

I gave the components "serious" Lithuanian names partly because they fit and partly because naming software is more enjoyable when one can avoid another six-hour meeting about whether “Nimbus” communicates sufficient strategic alignment.

Making the desktop responsive instead of merely pretty

Drawing translucent panels is easy compared with keeping input responsive while the operating system is faulting pages, reading compressed blocks over NFS, launching a process, and compositing a full screen on elderly hardware.

Fasadas was designed around not blocking the input path.

A dedicated input thread owns the pointer. It moves the cursor with a single hardware-cursor-plane register write. It only ever attempts to lock the framebuffer with trylock, and it routes enter, leave, and motion events independently. Shadow blurs run on a worker thread. Each major component creates a small spawn helper near the beginning of main(), so launching an application does not require forking a process while the parent holds a full-screen back buffer.

Native applications and foreign toolkits

Fasadas applications are distributed as .aop bundles. The current collection includes:

  • a terminal implementing a full VT100, VT220, and xterm-style state machine over a kernel PTY running bash;
  • a Finder-style file manager with drag-and-drop and desktop icons;
  • a System Preferences-style application with 18 panels;
  • a text editor;
  • a system monitor;
  • a hardware profiler;
  • a paint program;
  • a media player using the ported FFmpeg stack, with audio as its master clock.

The more interesting part is that existing graphical toolkits are not simply emulated inside one giant compatibility window.

GTK3 has a real GDK fasadas backend that renders its widget tree directly into a shared surface. Qt6 has a Fasadas QPA plugin compiled into qtbase, including global-menu support and no DBus requirement. GNUstep's libs-back contains a GSDisplayServer subclass, allowing an AppKit application to display an actual NSWindow while its NSMenu appears in the global menu bar.

For software that still expects X11, XFasadas acts as a rootless bridge. Unmodified X applications appear as individual native Fasadas windows, with damage-based partial updates rather than a full nested desktop.

What AnotherOS actually is

Technically, AnotherOS is a from-scratch 32-bit i686 operating system with its own kernel, userland, desktop, installer, and package manager.

It is not a Linux distribution, not a Linux fork, and not a BSD derivative. The kernel is MIT-licensed and currently contains roughly 100,000 lines of freestanding C and assembly across 135 source files. The native userland code adds roughly 50,000 lines, excluding imported ports.

What AnotherOS shares with Linux is an interface: the Linux/i386 int 0x80 system-call ABI. This interface is implemented faithfully enough that musl, BusyBox, GNU bash, GTK3, Qt6, WebKitGTK, GNUstep, and many other packages can run on the system. Everything beneath the ABI boundary is AnotherOS code.

The system boots through Limine using Multiboot2, on BIOS and UEFI machines, with GRUB2 available as a drop-in fallback.

The default boot entry mounts a compressed SquashFS root filesystem on demand and unions it with an in-memory tmpfs through OverlayFS. The complete userland, including the toolchain, lives on /; there is no required second disk.

AnotherOS also boots disklessly. PXE loads the kernel and a small initramfs, after which an in-kernel NFSv3 client mounts the remote storage and demand-reads the SquashFS image through NFS.

Yes, the operating system can boot over the network, mount a compressed root over another network filesystem, launch a graphical desktop, and then use Wi-Fi. This is approximately the moment when “small hobby experiment” stopped being an accurate description.

The kernel: all cores invited

SMP and scheduling

AnotherOS supports symmetric multiprocessing. Every processor runs threads.

The kernel performs LAPIC and IOAPIC initialisation using the ACPI MADT, starts application processors through the INIT-SIPI-SIPI sequence, installs per-CPU GDTs and timers, and performs cross-core TLB shootdowns.

The scheduler is MuQSS-derived in design. Each CPU has an earliest-virtual-deadline skip-list runqueue. Scheduling accounts for both niceness and latency niceness, supports CPU affinity, and can inspect other CPUs for load balancing.

Only normal nice-based tasks are currently implemented. Real-time policies such as SCHED_FIFO, SCHED_RR, and SCHED_ISO are absent. CPU-frequency scaling and NUMA/SMT awareness are also not implemented, although the architecture contains documented extension points.

That is the honest version. The dishonest version would be to mention the hook points in bold and quietly hope nobody asks whether the code behind them exists.

Killing the big kernel lock

The original system used a big kernel lock. That was useful while the kernel was small because it converted many concurrency problems into one extremely large concurrency problem.

The permanent BKL is now gone.

interrupt_dispatch operates without it by default. bkl_enter is effectively a no-op unless the bkl boot option restores the old one-CPU-inside-the-kernel model for debugging and regression bisection.

Subsystems now own their locks. These include:

  • the heap;
  • the process registry;
  • per-process file-descriptor and signal state;
  • a re-entrant sleeping filesystem lock;
  • per-address-space memory-management read/write locks, allowing page faults in different processes to proceed in parallel;
  • a leaf scheduler lock held across context switches in an xv6-style design;
  • a BKL-free system-call fast path;
  • a nobkl system-call class covering most file and pathname operations.

The changes were not merely architectural decoration. Measurements exposed a problem: placing work on a remote CPU's runqueue did not wake that CPU if it was halted. Adding a reschedule IPI to idle remote cores improved the fairness ratio between two identical filesystem readers from about 12× to 1.11×. The cost of an ext2_lock contention fell from 9,616 microseconds to 679 microseconds.

A scheduler that uses every core except the sleeping ones is technically multiprocessor support, but only in the way that owning several cars with flat batteries is technically a fleet.

Memory management: where optimism meets page faults

AnotherOS implements demand-paged execve using VMAs and page-fault fills, copy-on-write fork, mmap, mremap, madvise, a shared-library page cache, a slab allocator, and demand swap.

Swap activation validates a complete SWAPSPACE2 header before touching a region and never writes a header automatically. Accidentally “initialising” the wrong partition would be a surprisingly efficient filesystem-erasure feature, so the kernel declines to offer it.

The out-of-memory path attempts recovery in stages:

  1. drop page-cache pages;
  2. move eligible pages to swap;
  3. kill the largest non-critical process.

PID 1 and the compositor are protected. Killing the desktop to preserve a forgotten background process would be internally consistent but not especially user-friendly.

Demand paging transformed practical performance. A 39 MiB Qt executable now reaches full initialisation in seconds. Before demand-paged loading, execve read and copied the entire file up front, taking roughly 150 seconds.

One design constraint produced several difficult bugs: the kernel heap has a 160 MiB virtual-address window. That limit is unrelated to the amount of physical RAM installed, and the same heap backs tmpfs file data. At one point, kmalloc could not distinguish “the address window is full” from “physical frames are temporarily unavailable.” It retried forever while holding fs_lock, leaving the kernel technically alive and userland completely frozen.

This is a particularly rude class of bug. The machine has not crashed, so it remains available to disappoint you indefinitely.

Filesystems and storage

AnotherOS supports a surprisingly broad storage stack:

  • ext2 read/write;
  • ext3 journal replay through JBD;
  • ext4 extent reads and in-place writes;
  • SquashFS 4.0 with an internal DEFLATE implementation and three interchangeable backing stores;
  • OverlayFS with whiteouts and copy-up;
  • ISO9660;
  • NFSv3;
  • procfs;
  • a Linux-shaped sysfs.

The block layer supports ATA with PCI bus-master DMA reads and writes across all four IDE devices, retaining PIO only as a fallback where bus mastering is unavailable. It also supports AHCI/SATA, NVMe, virtio-blk, and USB mass storage, exposing devices through /dev/sd*.

SquashFS provided one of the clearest optimisation wins. Demand paging requests one 4 KiB page at a time from a 128 KiB compressed block. Without a decompressed-block cache, the kernel inflated the full block, used 1/32 of it, discarded the remaining 31/32, and repeated the work on the next fault.

After adding a cache, reading the same warm 4 MB file on real hardware fell from 2.51 seconds to 0.34 seconds, a 7.4× improvement for exactly the same bytes.

Computers are excellent at repeating pointless work very quickly. Performance engineering often consists of persuading them to stop.

Devices, graphics, audio, and LinuxKPI

USB support covers UHCI, OHCI, EHCI, and xHCI controllers. Class drivers bind per interface rather than assuming an entire device belongs to one class. The HID implementation parses report descriptors using a freestanding parser that treats input as hostile, because USB descriptors are supplied by devices and devices occasionally behave like they were designed by a committee during a power outage.

Audio uses an OSS-style interface over Intel HDA, AC'97, ES1370, and SB16. ACPI support includes S5 power-off and S3 suspend-to-RAM.

A Linux-driver compatibility layer lives under linuxkpi/. It maps a selected subset of Linux kernel interfaces onto AnotherOS primitives. Loadable ELF32 .ko modules can bind through it.

Current graphics modules include:

  • bochs_drm;
  • i915_drm, including a BLT command-streamer ring verified on real Haswell hardware;
  • radeon_drm, supporting Evergreen/SUMO scanout, GART, vblank-latched flips, and a DCE4 hardware cursor plane;
  • nouveau_drm, which exists but remains unvalidated because neither test machine contains NVIDIA hardware and QEMU does not emulate a useful target for it.

“Code-complete but untested” is not the same as “working.” One of the project's rules is to preserve that distinction even when marketing would prefer a more adventurous adjective.

Networking: from 12 KB/s to something recognisable

The network stack provides IPv4, fragment reassembly, ARP, ICMP, DHCP per interface, UDP, and congestion-controlled TCP.

TCP includes:

  • per-connection 64 KB send buffers;
  • use of the peer's advertised receive window;
  • slow start;
  • congestion avoidance;
  • duplicate-ACK fast retransmission;
  • RFC 6298 retransmission timeouts;
  • Karn's algorithm.

When the proper TCP work landed, end-to-end throughput rose from approximately 12 KB/s to 8,157 KB/s.

Twelve kilobytes per second is technically networking. It is also a useful opportunity to remember dial-up internet with unusual emotional intensity.

The system also contains a DNS resolver and cache daemon, AF_PACKET sockets, libpcap support, a real tcpdump, remote UDP kernel logging, and drivers for e1000, e1000e, igb, atl1c, r8169, and tg3 hardware.

Wi-Fi from scratch

Wi-Fi is handled by a native soft-MAC 802.11 stack. It implements scanning, authentication, association, the WPA2-PSK four-way handshake, and a CCMP data path based on AES-CCM. The implementation matches RFC 3610 test vectors byte for byte and checks 48-bit packet numbers to prevent replay.

Three native radio drivers currently exist:

  • ath9k;
  • Broadcom BCM4313, including LCN-PHY analogue initialisation;
  • Intel iwlwifi DVM.

Both test laptops associate with a real WPA2 access point and obtain DHCP leases over the air.

There is currently no IPv6 and no multicast group-membership implementation. IPv6 will therefore continue expanding elsewhere until I find another summer.

A debugger for when the kernel stops cooperating

AnotherOS includes a kernel debugger using GDB's remote serial protocol over UDP.

A debugger that depends only on normal UDP delivery becomes unavailable precisely when the network stack or interrupt path fails. AnotherOS therefore provides three independent entry routes:

  1. normal UDP demultiplexing;
  2. polling from the timer interrupt;
  3. an LAPIC performance-counter NMI watchdog.

A severe hang can close the first two paths at once, while the non-maskable interrupt can still enter the debugger. The system has successfully backtraced a kernel wedged with interrupts disabled on real hardware.

The best debugging interface is the one that remains available after the bug has destroyed all the convenient debugging interfaces.

The userland: dynamically linked, including PID 1

AnotherOS uses musl 1.2.5 as its C library. The standing policy is dynamic linking throughout the normal userland, including PID 1.

The system also uses a shared libstdc++.so.6, and its C++ exception-unwinding path is explicitly tested during image boot. That may sound excessively specific. It became specific after the same path failed silently in three different ways.

Two cross-toolchain approaches exist:

  • the main shipping build uses i686-elf-gcc 13.2 with musl specification-file wrappers;
  • a first-class i686-anotheros-musl target uses binutils 2.42, GCC 13.2, and musl 1.2.5 under toolchain/.

A Canadian-cross native compiler is in progress. Ports detect the platform through a real __anotheros__ preprocessor definition rather than pretending to be Linux and hoping nobody looks too closely.

The ports tree currently contains 96 cross-built packages, including:

  • bash 5.2 with working job control on real hardware;
  • BusyBox;
  • git;
  • Python;
  • Perl;
  • Go;
  • Lua;
  • TCC;
  • OpenSSL and GnuTLS;
  • X.Org;
  • GTK3;
  • Qt6;
  • Mesa;
  • FFmpeg;
  • GNUstep;
  • WebKitGTK;
  • Epiphany;
  • Wine components;
  • DOOM, because an operating system without DOOM support is still awaiting peer review.

The POSIX layer includes process-group and session job control, a full termios line discipline using values from c_cc, kernel PTYs, ptrace(2), an on-device strace, flock(2), eventfd, timerfd, signalfd, epoll, and mount(2).

POSIX compliance is not a single switch one enables after lunch. It is a long collection of small semantics, edge cases, historical agreements, and tests written by people who correctly assumed that someone would eventually get all of them wrong.

Real hardware, where emulators stop being polite

AnotherOS does not live only in QEMU. It runs on two bare-metal laptops:

  • an Acer Aspire One 722 with an AMD C-60 Bobcat CPU, Radeon HD 6290 graphics, SB800 chipset, atl1c Ethernet, and Broadcom BCM4313 Wi-Fi;
  • a Dell Inspiron with an Intel Haswell processor, i915 graphics, r8169 Ethernet, AHCI storage, and an Intel Centrino Wireless-N 100 adapter.

Both machines netboot. PXE loads the kernel and a 5.4 MB initramfs, after which the SquashFS root is demand-read over NFS. Build 488 reaches desktop from a cold start in roughly 30 seconds.

Emulators are patient, documented, and generally consistent. Real hardware is an adversarial review conducted by components whose datasheets are incomplete.

Several examples made that clear.

The r8169 receiver can stop permanently after RxFIFOOver or RxDU unless a latched status register is acknowledged. The resulting machine silently disappears from the network while continuing to transmit its own logs. It is the networking equivalent of leaving a conversation while still shouting updates from another room.

On the AMD C-60 platform, two processors performing outstanding SiliconBackplane accesses simultaneously can hard-lock the machine. Wi-Fi and the Radeon desktop therefore require an explicit interlock to coexist.

USB timeout loops were running ten times longer than intended because delay_ms() rounded waits to complete 100 Hz PIT ticks. Fixing that reduced the Dell's USB probe time from 10,120 ms to 1,120 ms and total kernel boot time from 25.5 seconds to 16.8 seconds.

Testing the hardware without photographing the screen

Hardware testing needed its own mechanism. Writing to /dev/mouse and /dev/kbd injects events through the same internal paths used by real interrupt handlers. Downstream code cannot distinguish a synthetic event from a physical one.

This allowed tests to verify more than “pixels appeared.” A test can move the pointer, trigger dock magnification, click an application, wait for a terminal, and confirm a bash prompt.

The final state is verified by obtaining a framebuffer capture over SSH and decoding it, rather than relying on photographs of laptop screens. Photographs are excellent for social media and poor for deterministic testing.

More than 200 test harnesses and a distrust of success

Approximately 217 harnesses live under docker/scripts/.

They boot the system headlessly, inject input, wait for serial markers, dump the display to PNG, and assert on geometry where a screenshot alone cannot answer the question.

What still does not work

A hobby operating system becomes more credible when its limitations are listed clearly rather than hidden beneath screenshots.

This is not a polished consumer operating system. It is a working experimental platform with known edges, measured failures, and a growing ability to explain its own behaviour.

That is much more interesting to me than a demo that never admits where the bodies are buried.

The AI did not “write an operating system” by itself

At this point, someone will inevitably say either:

“AI is useless. It cannot build anything real.”

or:

“AI built the whole operating system automatically while you slept.”

Both statements are wrong.

The agents produced a large amount of code. They helped port software, investigate failures, write tests, compare traces, implement drivers, restructure subsystems, and maintain documentation. They also hallucinated, duplicated code, forgot architecture decisions, ignored existing implementations, misunderstood deployment hosts, and occasionally announced success because a file existed somewhere.

The difficult part was not typing every line manually. The difficult part was organising the work so that generated code converged toward a coherent system.

That required:

  • dividing the project into owned subsystems;
  • defining interfaces before parallel work;
  • maintaining machine-readable and human-readable project memory;
  • preserving known-good checkpoints;
  • assigning independent verification tasks;
  • requiring measurable success criteria;
  • recording failures and rejected approaches;
  • stopping agents from editing the same fragile code simultaneously;
  • deploying and testing continuously on QEMU and real hardware;
  • recognising when the model had become confused and resetting the context.

Language models sometimes lose the plot near the end of a large context window. They may forget which PXE host receives a deployment, which Markdown file is authoritative, or whether a subsystem was replaced two days ago. Compacting context can make this worse if the project memory is not organised carefully.

The answer is rarely a mystical prompt. It is project management.

An AI agent behaves like an extremely fast, extremely literal, occasionally delusional employee who has read most technical documentation ever published but may forget yesterday's meeting. A good manager can obtain remarkable results. A bad manager can obtain ten incompatible implementations of the same spinlock before breakfast.

Learning to organise the night shift

The operating system became only part of the experiment. The more valuable lesson was learning how to organise autonomous work.

I began preparing tasks at night so that agents could continue while I slept. In the beginning, this did not produce pleasant mornings. An agent would encounter a failure at 02:13, become uncertain, and spend the rest of the night explaining why it could not proceed.

Eventually I learned to specify fallback strategies:

  • continue after non-critical failures;
  • preserve logs;
  • try the next test path;
  • revert only the affected change;
  • do not rewrite unrelated code;
  • leave the tree buildable;
  • record exactly what remains unresolved.

The famous instruction “continue no matter what” is funny, but dangerous without boundaries. An agent that truly continues no matter what may solve the build by deleting the test, redefining success, or creating a second operating system in a temporary directory.

The real instruction is closer to: continue independently within explicit constraints, preserve evidence, and stop before causing irreversible damage.

This skill is portable. The same orchestration techniques can be used for larger applications, infrastructure projects, testing systems, documentation, migration work, and long-running investigations.

The progress of an AI-assisted project depends less on the model's advertised intelligence than many people expect. It depends heavily on the operator's patience, clarity, discipline, ability to decompose work, and willingness to verify everything.

In other words, the AI did not remove management. It made management compile.

Why “AnotherOS”?

The name is not the result of a branding workshop, a domain-name auction, or a visionary dream involving mountains.

I already have software whose names begin with Another. I plan to port more of it to this operating system. Calling the project AnotherOS makes the family consistent and saves me from inventing a dramatic name that will later sound like a cryptocurrency.

The name also says exactly what the project is.

The world already has many operating systems.

This is another one.

What the project opens up

AnotherOS now combines several capabilities that are individually common in mature systems but unusual in a young hobby OS:

  • a real, useful syscall ABI;
  • a real musl-based userland;
  • dynamic linking throughout the normal system;
  • a versioned package layout with instant rollback;
  • an original desktop protocol;
  • native GTK3, Qt6, and GNUstep backends;
  • an X11 compatibility bridge;
  • a functioning browser stack;
  • SMP without a permanent big kernel lock;
  • native Ethernet and Wi-Fi;
  • real graphics acceleration;
  • extensive automated testing;
  • two bare-metal machines that boot and run it.

Because the exposed ABI follows Linux/i386 conventions, much of the recurring porting cost has already been paid. New packages can be expressed as recipes and built by a service that watches the ports repository and publishes packages automatically.

Because the desktop protocol is small and owned by the project, adding another toolkit backend is measured in thousands of lines rather than requiring a complete research project. A Wayland shim can be implemented as a client instead of replacing the desktop architecture.

Because the BKL has been removed and locking is split by subsystem, remaining scalability problems are bounded and identifiable. The sqfs_lock cold-start convoy is one lock with a known path, not proof that the whole kernel architecture must be discarded.

Because architecture-specific assumptions are confined to kernel/arch/i686/, a future x86-64 version should be a port rather than a total rewrite.

Most importantly, the project provides a place where questions can be followed through the entire stack.

“Why does the browser not execute JavaScript?” can begin as a rendering symptom, pass through WebKit and musl, and end in the kernel's thread-ID allocator.

“Why did adding CPUs make startup slower?” can lead from the compositor through page faults, SquashFS decompression, a lock held over NFS, and the scheduler.

“Why did the laptop vanish from the network while continuing to send logs?” can end in a latched r8169 interrupt status bit.

These are not abstract diagrams. They are working paths through a system that can be changed, rebuilt, netbooted, measured, and broken again before lunch.

So, was it worth it?

Absolutely.

Not because AnotherOS is ready to replace anyone's daily operating system. It is not. Not because an AI agent can now autonomously manufacture a flawless kernel. It cannot. And not because writing operating systems is an efficient way to spend a summer. On that question, the evidence is strongly against me.

It was worth it because the project changed how I think about AI-assisted development.

I began with the suspicion that I might be too old to keep up with the new tools. Instead, I learned that decades of systems experience become more useful when paired with agents that can execute well-scoped work quickly. The models supplied speed. Experience supplied direction, scepticism, architecture, and the ability to recognise when a “successful” result was nonsense.

The project also reminded me why I became interested in computers in the first place. Not to consume another platform, but to understand one. Not to install one more abstraction, but to remove enough abstractions to see the machine underneath.

In 2003, on an old 80386SX, I tried to build a DOS clone and produced something incomplete that I never forgot.

More than two decades later, I returned to the same idea with faster hardware, much larger ambitions, and a team of artificial colleagues who never sleep, occasionally lie, and require surprisingly detailed supervision.

The result is approximately 100,000 lines of kernel code, another 50,000 lines of native userland code, 96 ported packages, more than 200 test harnesses, a graphical desktop, native networking and Wi-Fi, SMP, dynamic linking, real hardware support, and enough unresolved bugs to guarantee that I will not need another hobby soon.

Why build AnotherOS?

Because I wanted to.

Because it was difficult.

Because I learned more from making it work than I would have learned from another course explaining how to ask a chatbot for a to-do application.

And, finally, because I can.

Videos