Operating System Project

MonkeOS

Project site + design article | Posted | Back to blog archive | Home

MonkeOS is a full-stack Rust operating system project that tries to own the entire path from boot to login, shell, desktop, storage, media, networking, and native applications without hiding behind borrowed abstractions.

This page now works as both the public front door for the project and the long-form design article explaining how the system is built, what it currently prioritizes, and why those choices were made.

Project Overview

  • Scope: kernel, VFS, shell, login, desktop, media, networking, and bundled apps.
  • Language: Rust-first, with no_std pressure shaping early design.
  • Boot path: GRUB + Multiboot2 so the project can spend energy on the OS itself.
  • Repository: codeberg.org/coops/monkeos
  • Design bias: native ownership, explicit boundaries, and build-time certainty.

Current Focus

MonkeOS is trying to become more than a bootable demo. The current shape points toward a coherent native environment: smoother desktop behavior, stronger runtime contracts, better graphics and media paths, cleaner session handoff, and a build flow that produces deterministic assets the OS can truly own.

What Makes It Distinct

  • The shell remains central instead of being treated as an embarrassing temporary layer.
  • The desktop is native and tightly connected to kernel and runtime decisions.
  • Bundled userspace and Monk tooling are part of the system story, not decorative extras.
  • The repo is allowed to show real boundaries instead of pretending everything is already polished.
Long-Form Design Notes

The project summary ends here. What follows is the deeper design article: the reasoning, tradeoffs, and architectural choices behind MonkeOS as it exists today.

What MonkeOS Is Trying To Be

MonkeOS is not trying to win an operating systems purity contest. It is not trying to be a teaching kernel that stops right before things become annoying, and it is also not trying to cosplay as a Linux replacement before the plumbing exists. The central design choice is much simpler: MonkeOS is a full-stack x86_64 OS project that wants to own the entire boot-to-desktop path in one tree, in one language-first environment, with as little hand-waving as possible.

That sounds obvious, but it has consequences everywhere. If you decide you want a real boot path, you need to deal with GRUB, Multiboot2, paging, interrupts, framebuffers, PCI scanning, timer behavior, and all the early machine-state nonsense that people skip when they only want to demo a shell prompt. If you decide you want native apps instead of screenshots of aspirations, you need windows, input routing, a filesystem, a runtime story, a font system, audio transport, and enough scheduling that the whole thing does not collapse when more than one component wants CPU time. MonkeOS exists in that uncomfortable middle ground where the project is too real to be a toy, and too unfinished to pretend every subsystem is settled.

That middle ground explains a lot of the repo. Some things are surprisingly complete because they were unavoidable. Some things look rough because they were forced into existence before the lower layers were mature enough. Some things are duplicated because the project had to learn the hard way which abstractions were worth keeping. If there is one design rule that keeps coming up, it is this: MonkeOS prefers a subsystem that actually works today over a cleaner-looking subsystem that only works in a README.

Why Rust, And Why no_std First

Using Rust for MonkeOS is not a marketing line. It is a project-shaping decision. Rust buys the project several useful properties at once: explicit ownership, a much harder time accidentally corrupting memory in boring code, strong enums for protocol and state-machine work, and a type system that is genuinely good at forcing boundary decisions between layers. In kernel code that matters. It matters when dealing with device state, it matters when dealing with loader data, it matters when keeping UI code from smearing across runtime code, and it matters when trying to grow a syscall surface without turning the whole kernel into a pile of untyped special cases.

The no_std part matters just as much. MonkeOS does not inherit a comfortable runtime, so the project had to learn early which pieces are truly essential. You do not get to quietly assume threads, file handles, heap semantics, I/O buffering, argument parsing, or system calls from some older host environment. If the project wants allocation, it has to provide an allocator. If it wants strings in risky places, it has to justify them. If it wants logging, it has to be explicit about where the bytes go. That pressure is annoying, but it keeps the design honest. It stops the system from quietly depending on invisible host behavior and then calling that independence.

There is a tradeoff, obviously. The project gives up a giant amount of ready-made software by choosing this path. That is why MonkeOS has to build native apps, its own shell behavior, its own lightweight browser surface, its own media bundling flow, and its own compatibility experiments. But that trade is also the point. The repo is meant to be a system, not just a container for porting somebody else's assumptions.

Why GRUB And Multiboot2 Instead Of A Custom Bootloader

This is one of the clearest practical choices in the whole system. MonkeOS uses GRUB and a Multiboot2 handoff because writing a fully custom bootloader at the same time as a kernel, desktop, filesystem, and userspace runtime is how a hobby OS turns into six half-finished projects. GRUB solves the early boot portability problem well enough, and Multiboot2 gives the kernel a structured way to receive framebuffer information and boot metadata. That means MonkeOS can spend its complexity budget on kernel bring-up and runtime work instead of spending months proving that it can also reimplement the loader side from scratch.

There is no shame in that. In fact, it is a healthy design choice. MonkeOS is not trying to be “minimal” in the sense of inventing every lower layer just to say it did. It is trying to be coherent. GRUB gets the project onto both BIOS and UEFI systems. The kernel takes over from there. The handoff is explicit. The division of labor is clear. This is one of those cases where boring is good.

Why The Kernel Is Monolithic

MonkeOS is monolithic because the project values iteration speed and subsystem intimacy more than theoretical isolation at this stage. A microkernel would force more message-passing boundaries early, more duplicated data marshaling, more process semantics before the process layer is ready, and more pain whenever the system wants to do something simple like boot, log, allocate memory, mount MonkeFS, bring up the framebuffer, and then hand off into a shell or desktop. Those boundaries may make sense later, but too much early isolation would mainly slow down a project that is still discovering what its stable interfaces even are.

That does not mean MonkeOS has no boundaries. It does. The source tree separates boot, memory, interrupts, audio, VFS, networking, UI, desktop, and userspace glue. There are API modules. There are dedicated app entry points. There is a session layer. There is a builtins layer. There is a distinction between internal kernel work and bundled native applications. The point is that these boundaries are source and runtime boundaries within one kernel image, not cross-process RPC boundaries everywhere by default.

This is also why the repo can move quickly when a subsystem needs surgery. If the compositor timing is wrong, the project can touch interrupts, desktop chrome, and the host compositor without pretending those are three separate worlds. If login needs to happen before desktop startup, the kernel and session code can be changed together. A monolithic design makes certain classes of mistakes easier, but it also makes whole-system refactors much more attainable in a young codebase.

Memory Management: The Point Is Control, Not Elegance

Memory management in MonkeOS is intentionally direct. The kernel wants control over physical memory, virtual mappings, and the allocator because almost every other system depends on those decisions. The graphics path depends on it. The userspace loader depends on it. The VFS caches depend on it. The desktop depends on it. The audio queue depends on it. Every time the project hit an out-of-memory condition while trying to decode an image or stream media, that was not just a single bug; it was a reminder that the kernel cannot outsource its memory discipline.

This explains why MonkeOS often chooses bounded or staged behavior instead of “just decode the whole thing”. The system resizes wallpaper assets for boot-safe paths. It prefers predictable caches. It often limits render targets instead of painting at whatever size a window happens to occupy. It uses static backing areas in a lot of low-level places because the question is not “is this the cleanest code” so much as “does this survive the worst obvious case without panicking the machine”.

In a mature OS you can hide a lot of that behind polished APIs. In MonkeOS the design choice is visible on the surface. You can see it in the login wallpaper path, in the WebM bundling flow, in render-target sizing, and in the fact that the project is constantly evaluating whether an allocation is justified or just convenient.

Interrupts, Time, And Why Smoothness Became A Design Issue

A lot of operating system articles talk about interrupts like a checklist item. MonkeOS does not really get to do that, because timer behavior leaks directly into user-visible quality. Once the project grew a desktop, games, animation, and media playback, the timer frequency stopped being a hidden implementation detail and became part of how “real” the system felt. One of the biggest smoothness fixes in the tree came from discovering that different parts of the system disagreed about timing assumptions.

That matters because a compositor that advances motion on the wrong delta time feels janky even if every individual frame is technically valid. A boot intro paced awkwardly against the timer feels cheap. A game that repaints the cursor path with itself makes the whole desktop feel slower than it is. MonkeOS therefore treats timer setup and frame pacing as architecture, not polish. The PIT frequency is not just a kernel constant; it is part of the visual and interactive character of the OS.

This is a recurring MonkeOS pattern: once the system grows a visible layer, low-level correctness turns into UX. That is why interrupt setup is not “done” just because the kernel can count ticks. It is only done when those ticks produce consistent behavior across desktop, apps, audio, login, and boot effects.

Why MonkeFS Exists Instead Of Leaning On A Ported Userspace Stack

MonkeFS plus the VFS layer exists because MonkeOS needs a storage story that the kernel understands deeply. It needs predictable mount points, a place for bundled userspace assets, a place for desktop preferences, a place for runtime state, and a filesystem model that can be reasoned about during boot and login without pretending there is already a normal host userspace beneath it. MonkeFS is therefore not only about persistence. It is also about making the rest of the OS design possible.

The VFS matters because it prevents the rest of the tree from caring too much about the exact backing store. Native apps use logical paths. Settings can ask for wallpapers through a high-level interface. Userspace assets can be bundled into the in-memory filesystem at startup. The login path can treat storage differently before and after unlock. The same system can expose built-in assets like logos, fonts, media, and scripts without every app knowing which bytes were baked into the kernel and which came from a file node.

This is also where MonkeOS starts to show its “own OS” ambitions more clearly. The project is not just serving files. It is defining what a MonkeOS environment looks like: where bundled media lives, where shell-visible commands live, where desktop preferences live, and how login state interacts with storage access.

Why Bundled Userspace Exists At All

The bundled userspace layer is one of the most important design choices in the repo because it lets MonkeOS grow before general-purpose compatibility is finished. The system can install shell tools, static assets, logos, media, scripts, and native app entry points directly into the VFS at startup. That means the OS has something meaningful to run immediately, which is critical for a project that is not yet pretending to launch a broad ecosystem of foreign binaries.

This bundled layer also keeps development concentrated. Instead of trying to solve package management, dynamic linking, and third-party ABI expectations all at once, MonkeOS can build a native app, wire it into the desktop, ship its assets, and actually use it. Files, Settings, Welcome, Task Manager, the terminal surface, media viewing, and browser work all benefit from that decision. It gives the OS an identity before external compatibility is ready.

The project still keeps an eye on ELF loading and a Linux-style syscall direction, but that is treated as future growth, not as something that must block current usefulness. That is a key design posture throughout the tree. MonkeOS would rather be a coherent native system today than an incoherent “compatible” system tomorrow.

Why The Shell Stays Central Even With A Desktop

This is maybe the most important philosophical choice in MonkeOS. The shell is not a fallback embarrassment. It is a primary runtime surface. That matters because it changes how the project makes decisions. If the desktop is glitchy, the shell still has to work. If login changes, the shell still has to reflect the authenticated user. If a built-in tool is valuable, it belongs in the shell even if a GUI wrapper exists somewhere else.

That is why the builtins are substantial. They are not token demo commands. They report memory, processes, filesystems, graphics state, network state, audio state, app management, and OS metadata. The shell is where the system explains itself. It is also why MonkeOS drifted back away from turning the terminal into a mini GUI app. The project already has windows. The terminal did not need fake chrome inside the window. It needed to remain practical as a command interface.

The deeper reason is architectural honesty. A desktop-first OS can hide its guts until it fails. A shell-first subsystem exposes them every day. MonkeOS benefits from that because it is still evolving. The shell forces the OS to keep a clean enough internal story that basic inspection and control remain possible.

Desktop, Window Management, And Why The UI Exists Even Though It Is Not “Finished”

MonkeOS ships a desktop because a native desktop is one of the project goals, but it does not treat the desktop as the only valid runtime. That balance produced the current shape: a desktop environment with a top bar, start menu, dock, login screen, settings, windows, and native apps, layered over a still-experimental compositor and window manager path. The desktop exists because the project wants to answer real questions about input routing, window resizing, proportional fonts, menus, titlebars, fullscreen, minimized windows, and app lifecycle. Those questions cannot be answered from a shell prompt alone.

The project’s experience here has been educational in a painful way. Every attempt to make the desktop feel more polished exposed some lower-level truth: wrong frame timing, stale dirty regions, inconsistent title metrics, mismatched content rects, software cursor coupling, text systems that assumed bitmap widths, UI layout math that chopped strings too early, and menus that looked fine until real text hit them. None of that is abstract. It is the actual work of making an OS surface feel intentional.

The important design choice is that MonkeOS keeps the desktop native. There is no hidden giant toolkit port doing all the hard work. There is an in-tree UI path, native app entry points, and explicit ownership of window behavior. That makes the system rougher than a mature desktop stack, but it also means every improvement genuinely belongs to MonkeOS rather than being rented from somewhere else.

Why Fonts Became A Serious Architectural Subject

Font rendering sounds cosmetic until a fake or half-wired font path makes the whole OS look wrong. MonkeOS learned this the hard way. Bitmap fallback paths, fixed-cell assumptions, proportional glyph widths, text clipping, titlebar centering, and app layouts all turned font work into a system-level concern. Once the project moved toward a real TTF loader and smoother UI rendering, a lot of unrelated-looking bugs suddenly became visible for what they were: layout bugs, not “font bugs”.

That is why the project now treats font choice and font face as desktop preferences, not just renderer trivia. The shell wants a terminal-appropriate face. The desktop wants a readable sans. Settings wants actual wrapping and clipping behavior that respects the selected font. Window titles want symmetric safe areas so the text looks centered even when controls occupy one side. The login screen wants a smoother logo and cleaner composition. The design lesson is that text rendering does not sit under UI; it is part of UI structure.

Graphics: Software First, Hardware Where It Helps

MonkeOS does not wait for a perfect GPU stack before drawing things. That is a deliberate choice. The system keeps a software-rendered path for apps like Gears, image demos, Doom scaling, the task manager, login, and the desktop surface itself. The advantage is obvious: the OS can keep making progress on windows, rendering, and UI behavior without blocking on a complete accelerated graphics story.

At the same time, the project does not ignore hardware. There is virtio GPU work, DRM/KMS bridge work, firmware loading paths, and a growing graphics subsystem that acknowledges the difference between “I can draw pixels” and “I can present them efficiently on real targets.” This split is important. MonkeOS does not fetishize software rendering, and it does not fetishize acceleration either. It picks whichever layer lets the system move forward while keeping control over the presentation path.

This is also why the repo can host both a software gears demo and a DRM-oriented bridge without contradiction. They solve different problems. One proves the app and raster path. The other grows the platform toward future compatibility and better hardware integration.

Audio: Real Transport Matters More Than “Has Audio Code”

MonkeOS audio has gone through the same reality check as graphics. There is no point claiming audio support because the tree can generate a tone if the actual backend does not feed hardware consistently. That is why the project now distinguishes between backend selection, mixer behavior, queued PCM, transport initialization, and real hardware or VM paths like AC97 and HDA.

The kernel mixer exists because MonkeOS wants a central submit path under kernel control. Test tones, boot intro audio, and media playback all rely on that. But transport still matters. The system has to decide whether AC97 or HDA is alive, keep a ring full enough to avoid constant underruns, and make sure volume/mute behavior applies at the right stage. This is why the audio driver code is not just a parser collection. It is a runtime subsystem with scheduling and buffering implications.

Networking: Useful First, Complete Later

The network stack follows a very MonkeOS approach. It uses a real in-tree TCP/IP path, smoltcp-based socket work, DHCP bring-up, DNS, simple HTTP/HTTPS fetch, and enough surface area to support the browser, weather experiments, and diagnostics. It does not pretend to be a full Linux networking universe. It is meant to be useful inside MonkeOS first.

This is why the browser can exist even though it is deliberately narrow. Champanzee is not trying to impersonate a full modern browser. It is a native browser surface that proves the OS can fetch pages, render text, manage an address bar, and integrate network activity into a normal app lifecycle. In the same way, the networking builtins in the shell expose state because MonkeOS wants inspectable behavior, not magic.

Why Login, Sessions, And Encrypted Storage Sit Above The Kernel But Before The Desktop

The login path in MonkeOS is a good example of a design becoming more serious over time. At first, a shell-first experimental OS can get away with “boot and run.” But once the project grows a display manager, session selection, and user-facing startup flow, it becomes important that login happens before the desktop actually owns the screen. The project also wanted a non-bypassable feel to the login handoff, which pushed the design toward storage gating rather than just a fake dialog on top of a running session.

The result is a pre-desktop login layer that picks a session and then unlocks the user environment. It is not yet a bootloader-stage full-disk-encryption stack, and the project is honest about that. But it does mean the login screen is part of system structure, not just a theme element. It exists because MonkeOS wants a real transition between boot, authentication, and runtime session start.

Monk, .mnk, And Why MonkeOS Wants Its Own App Story

The Monk language and .mnk tooling exist because MonkeOS is not content with being only a kernel and some native app code. The project wants its own scripting and app narrative too. That means a compiler path, a run path, builtins that understand the format, and a filesystem/runtime environment that can host those files naturally.

This is consistent with the rest of the design. MonkeOS keeps building internal capability instead of waiting for external compatibility to solve identity. Monk may not be the final answer to every application question, but it is a useful expression of the same philosophy behind MonkeFS, native apps, and bundled userspace: if the OS wants to be more than a shell around other people’s software, it needs to cultivate at least part of its own software model.

Build Tooling: The Project Wants To Be Buildable By Humans

Build systems say a lot about a project’s values. MonkeOS keeps the build flow relatively direct: cargo for the kernel, a dedicated mkiso helper for ISO construction, and thin wrapper scripts for Linux and Windows. The tooling is not trying to be clever. It is trying to be legible. The project already has enough complexity in the kernel; it does not need a mysterious meta-build tower on top of it.

This is also why the repo has been slowly cleaned up around stale dependencies, fake compatibility claims, and dead workspace members. A build that “mostly works except when it points at deleted code” is worse than a build that is simply strict. MonkeOS’s build tooling increasingly aims to fail for real reasons and explain the missing part honestly, like GRUB package requirements or missing UEFI boot pieces in WSL.

Why MonkeOS Does Not Pretend To Be Linux-Compatible Yet

This is another deeply intentional design choice. MonkeOS is willing to grow Linux-style syscalls, ELF loading, userspace expectations, and DRM-oriented bridges, but it does not claim that broad compatibility already exists. That restraint is healthy. A lot of hobby OS projects end up with a strange psychological split where the README describes a future ecosystem and the codebase is still fighting with window redraws. MonkeOS tries, imperfectly, to resist that temptation.

The project would rather say “native app path first, compatibility later” than bolt on a fake veneer of external software support. That is why Wayland is treated as a possible future layer instead of a magic answer to compositor smoothness. That is why the browser is native. That is why the task manager became a native app instead of a browser page. That is why the terminal remains useful on its own terms. The system keeps choosing inward coherence over outward theater.

A Short Example Of The Project’s General Taste

One way to understand MonkeOS is to look at the kind of compromise it accepts happily. Here is a tiny pseudo-summary of how many subsystems in the tree are chosen:

if a layer is honest, debuggable, and makes the OS more usable today:
    keep it
else if a layer looks impressive but depends on three missing systems:
    cut it down until it stops lying

That is not elegant theory, but it describes the project pretty well. The shell stayed because it was honest. Native apps stayed because they were honest. GRUB stayed because it was honest. MonkeFS stayed because it made the rest of the OS possible. A lot of false polish got ripped out or reworked because it was not supported by the lower layers yet.

Core design thesis: MonkeOS chooses the full-system path it can actually own, even when that path is rougher than borrowing more of somebody else’s assumptions.

Further Technical Notes

How A Native App Actually Starts

One of the easiest ways to misunderstand MonkeOS is to assume that words like userspace, /bin, app, and native all refer to one single finished execution model. They do not. MonkeOS currently has several execution classes, and the repo is more honest for showing that unevenness instead of burying it under a fake uniform abstraction.

Some programs are shell builtins that exist because the shell needs direct control over system state. Some are bundled script payloads. Some are ELF experiments. Some are ring3-adjacent execution paths. Some are native desktop applications implemented directly in the kernel image but surfaced through a userspace-shaped interface. That last category is especially important, because it is where a lot of the project's practical software lives today.

A concrete example is kim. From the user's point of view it behaves like a normal tool. It can be invoked from the shell. It appears under a familiar path. It opens a normal window. It edits normal files. But the real editor body is not an external userspace ELF. It is native MonkeOS code in src/kim.rs, launched through the app manager and wired directly into the VFS, keyboard pipeline, pointer events, window lifecycle, and TTF-backed text renderer. That is not a shortcut around a "real" implementation. It is the real implementation for the current operating system.

This is why MonkeOS installs marker nodes like /bin/kim even though the live implementation is native code. The VFS path gives the shell and file-oriented tooling a stable namespace. The app manager gives the runtime a stable launch surface. The native module provides the actual execution body. Those are three different roles, and MonkeOS does not gain anything by pretending they are the same thing.

Why Some Directories Look Empty Even When The Feature Is Real

Another source of confusion in the repository is the presence of directories that contain nothing except a README.md. In a lot of projects that would be a sign of abandoned scaffolding. In MonkeOS it often means something much more specific: the directory is a declared boundary, but the payload that belongs to that boundary is either optional, generated elsewhere, or still intentionally kept in the live runtime module.

The best example is .kim-seed. That directory is not where the editor implementation lives. The actual runtime editor is already present in src/kim.rs and is already integrated with the shell, app manager, and VFS. The seed directory exists to hold kim as a companion project root: a place for design notes, externalization plans, future assets, or porting notes that should not be forced into the hot runtime path until there is a concrete reason to do so.

Another example is src/userspace/webmbundle. That path is not supposed to contain the final runtime media representation. It is the source staging directory for optional developer-supplied .webm files. During the build, build.rs scans that directory, probes each source file, rescales it, transcodes the video into bounded BGRA frame data, transcodes the audio into bounded PCM, and emits a generated Rust manifest into OUT_DIR. At runtime, the OS exposes a VFS catalogue under /webmbundle that maps back into those compiled assets. So the directory can be README-only and still be functioning exactly as designed.

This distinction matters because MonkeOS is trying very hard not to blur source ownership, generated outputs, runtime namespace, and linked kernel data. If every layer were stored in one giant "easy to screenshot" folder, the project would be simpler to glance at and much harder to reason about correctly.

Build-Time Code Generation Is Part Of The Architecture

One of the more technical design choices in MonkeOS is how much responsibility has been pushed into build.rs. The build script is not just a convenience wrapper. It is a normalization layer between high-variance source material and the very bounded data shapes that the runtime can safely own.

Fonts are preprocessed into generated tables. The boot intro media path is pre-transcoded into a runtime-friendly format. Optional WebMonke assets are discovered and emitted into a generated bundled-media table. Metadata is materialized as Rust source, not just as loose files. This makes the runtime significantly less dynamic than a desktop Linux distribution would be, but in MonkeOS that is a strength. A kernel-first, no_std, memory-sensitive system benefits from knowing the exact pixel format, audio format, dimensions, frame counts, and lookup tables before boot.

Build-time generation also reduces a lot of false complexity. If the runtime already knows that a font has been rasterized into the dimensions the UI expects, or that a bundled video has already been limited to a sane size and sample format, then the runtime can stay small and deterministic instead of re-solving arbitrary decode and normalization problems in every path that touches those assets.

This is why generated outputs go into OUT_DIR instead of being committed back into the source tree. MonkeOS wants a strict separation between the editable source artifact, the generated normalized artifact, and the linked runtime artifact. That separation is not accidental housekeeping. It is how the project keeps source-of-truth boundaries sane.

The Runtime Namespace Is A Product, Not A Mirror Of The Repo

Related to the point above, MonkeOS does not try to make the runtime VFS tree mirror the repository layout exactly. That would be convenient for developers in the short term, but it would be a poor operating-system design. The runtime namespace is for applications and users. The source tree is for developers. Those concerns overlap, but they are not identical, so the system lets them diverge where that divergence produces a cleaner runtime model.

This is why the bundled userspace installer creates paths like /bin/sh, /bin/kim, /usr/share/monkeos.svg, and /webmbundle during system initialization. Some of those files are plain textual payloads. Some are symbolic entry points into native applications. Some are tiny VFS placeholders that route back into compiled media assets. All of them are valid because the VFS is a runtime contract, not a passive host-filesystem reflection.

The long-term advantage of this choice is flexibility. Once MonkeOS fully owns its runtime namespace, it can add session overlays, manifests, encrypted views, package registries, generated assets, or future compatibility layers without being trapped by the idea that the source tree and runtime tree must always be isomorphic.

Why The Process Model Still Looks Uneven

MonkeOS currently has an uneven process story because it is solving multiple layers of runtime identity at once. It wants shell-first control. It wants native desktop applications. It wants ELF loading. It wants some ring3 work. It wants bundled scripts. It wants a VFS that can expose user-facing tools before a fully generalized process model exists. That is a lot to solve in one young OS.

A more performative project might paper over that state with one slogan and a lot of wrappers. MonkeOS mostly does the opposite. It exposes the mixed reality of the current execution model: builtins are builtins, native apps are native apps, bundle-installed tools are bundle-installed tools, and experimental userspace remains experimental. The result is less aesthetically tidy, but it is much easier to debug and evolve because each path still reflects the real shape of the subsystem under it.

The important thing is that the project is not directionless here. There are already convergence points: a shared app manager, a shared VFS, a shared session layer, and a growing execution surface that can launch shell tools, native apps, or loaded artifacts through one broader runtime. The model is uneven because the OS is growing, not because the repo has no structure.

Window Manager And Desktop Environment Are Separate On Purpose

The newer session and login work brought another architectural choice closer to the surface: MonkeOS treats the window manager and the desktop environment as related but not identical layers. That may look premature in a system that only has one serious built-in graphical pairing, but it is actually a defensive design choice.

The window manager side owns frame behavior, focus, z-order, drag and resize semantics, fullscreen behavior, content rect calculation, and top-level window state transitions. The desktop environment side owns launcher behavior, top bar composition, dock behavior, settings integration, wallpaper use, desktop preferences, and other session-level affordances. Those responsibilities often interact, but they should not silently collapse into one undifferentiated blob or the system becomes much harder to evolve.

This is why MonkeOS has started introducing session selection and manifest-like structures for describing desktop and window-manager identity. The immediate practical value is modest, but the architectural value is large. It prevents today's single built-in stack from becoming tomorrow's accidental permanent assumption.

Why UI Bugs Keep Exposing Deeper Contract Problems

A recurring pattern in MonkeOS development is that "small" visual issues often turn out not to be small at all. Text clipping in settings can point to fixed-width assumptions leaking into proportional-font layouts. Window title drift can expose asymmetric safe-area math. Menus that cut off labels can reveal hard-coded widths where measured text should have been defining size. Fullscreen glitches can expose stale content-rect ownership between the window manager and compositor.

This is one reason the project spends so much time on what look like petty UI details. Those fixes are not only aesthetic. They are instrumentation for the correctness of the toolkit and compositor contracts underneath. A menu that sizes from content is proof that measurement is becoming real. A title that stays centered through drag, fullscreen, and resize is proof that the frame math has stopped lying. A settings card that wraps text correctly is proof that the text system and layout system are finally cooperating.

In a young native desktop, layout polish and architecture work are not separate categories. They are often the same bug seen from different distances.

Why Media Is Bundled And Transcoded Instead Of Fully Imported Live

The media story in MonkeOS is another place where the design chooses bounded ownership over broad claims. Instead of saying "the OS can decode any arbitrary container and codec live at runtime," the project currently prefers a bundled-media path where source media is transformed during the build into formats the runtime knows exactly how to present and mix.

The boot intro path and the WebMonke bundle path are both examples of this. The build system probes and transcodes the source media into controlled raw forms. The runtime consumes those forms with very little ambiguity. That sharply reduces the number of places where a large allocation, unsupported sub-format, or timing surprise can ruin boot or playback.

This is not the same thing as saying MonkeOS will never grow a fuller media stack. It is saying that the current runtime would rather own one deterministic path than pretend to own ten. For a system that is still tightening its memory discipline and presentation timing, that is the right trade.

Stability Usually Improves When Hidden Assumptions Get Deleted

Many of the project's stability fixes have the same shape. The code assumed the wrong timer rate. The code assumed a wallpaper decode buffer could be huge during early boot. The code assumed a draw loop could iterate unclamped dimensions after a resize. The code assumed a network fetch should wait for one particular connection shutdown pattern instead of checking whether the complete body had already arrived. The code assumed a bitmap-era text width constant would still be valid after a proportional TTF renderer took over.

What is interesting is that these are not wildly different classes of bug. They are all examples of the same architectural failure: the system believed an implicit contract that was never actually guaranteed. Once those contracts are made explicit, the code usually gets better in multiple dimensions at once. It becomes more stable, easier to inspect, and easier to explain.

That pattern is worth documenting because otherwise the repository can look like a random collection of tactical fixes. In reality, a lot of those changes are all moving the system in one direction: replacing invisible guesses with explicit ownership and explicit bounds.

Why Real Hardware Testing Changes The Project

MonkeOS has been tested on actual UEFI machines, and that matters for more than bragging rights. Real hardware changes which mistakes the project is allowed to get away with. Emulators are incredibly valuable, but they are also polite. They often present predictable devices, predictable timing behavior, and firmware handoffs that are easier to satisfy than the messy diversity of physical systems.

Once the OS boots on real hardware, questions that felt optional stop being optional. Audio backend choice matters more. Memory pressure matters more. Firmware assumptions matter more. Display handoff robustness matters more. The project can no longer hide behind the idea that "QEMU tolerated it, so the design is probably fine."

Real-machine testing therefore acts as architectural pressure, not just as a compatibility checkbox. It pushes the repo toward more honest initialization, more bounded resource use, and better separation between what is merely plausible in a VM and what is actually resilient on a machine that does not care about the developer's intent.

Concrete Implementation Paths

Boot Handoff In Actual Function Order

The real boot-to-session path in the current tree is short enough to write down exactly, and doing so explains a lot more than broad phrases like "the OS boots into login." The current control flow in src/main.rs is:

rust_entry(mb2_info_pa, boot_magic)
  -> kernel_main(boot_info)
     -> boot::init::init_kernel(boot_info)
     -> drivers::vga::enable_framebuffer_console(false)
     -> drivers::vga::clear_screen()
     -> userspace::webm::play_boot_intro()
     -> drivers::mouse::init()
     -> login::run()
     -> session::launch(selection)

That ordering is not decorative. The framebuffer console is explicitly disabled before the display-manager path so the desktop does not flash behind login. The boot intro runs before login, not inside the desktop session. Mouse initialization happens before the display manager starts its event loop. The desktop itself is not launched until session::launch() receives the selected session from login::run().

App Launch Is A Resolver Chain, Not A Single Primitive

The current execution model is easier to understand if you stop looking for one magical "run program" primitive and instead look at the actual resolution order. In the userspace bridge in src/userspace/mod.rs, execve(path, cwd) resolves execution in this order:

execve(path, cwd):
  1. apps::resolve(path)
  2. bundle::exec_blob(path)
  3. vfs::api::read_file(resolved_dir, name)
     3a. if suffix == .mnk -> monk::run_blob(...)
     3b. if shebang        -> run_script(...)
     3c. else              -> exec::load_elf_image(...)

That matters because MonkeOS is not pretending that native apps, scripts, Monk code, and ELF payloads are already one perfectly flattened category. The launch surface is unified enough to be useful, but the implementation still preserves which class of payload actually won resolution.

Kim's Launch Path, Concretely

kim is a good technical example because it touches almost every boundary at once. The current native launch path is:

/bin/kim marker installed by userspace::install_base_userspace()
  -> shell or launcher resolves "kim"
  -> userspace::apps::run(App::Kim)
  -> app_manager::launch(LaunchTarget::Kim { path, cwd })
  -> crate::kim::open(cwd, path)
  -> appui::create_window_at(...)
  -> live KimSession owns editor state

The key technical detail is that /bin/kim is a namespace marker, not the full executable body. The real editor is a KimSession struct in src/kim.rs that keeps window id, dimensions, VFS target, text buffer, cursor position, scroll state, editor mode, command buffer, dirty state, and syntax mode. That is why calling the README-only .kim-seed directory "the kim implementation" would be wrong. The actual implementation is already in the native session path.

What The App Manager Actually Tracks

The app manager in src/app_manager.rs is not a symbolic shell wrapper. It keeps a real runtime table of window-backed application sessions. The relevant types are almost a miniature process table:

pub enum LaunchTarget {
  ShellWindow, Doom, GlDemo, Kim, WebmViewer,
  PngDemo, JpgDemo, FileManager, Settings,
  Welcome, Champanzee, TaskManager
}

enum ManagedKind {
  Shell(TerminalSession),
  Doom(DoomSession),
  Gl(GearsSession),
  Kim(KimSession),
  Webm(MediaViewerSession),
  ...
}

It also treats some applications as "heavy" workloads for scheduling purposes. Doom, Gears, and WebMonke sessions participate in a different update rhythm than lightweight apps. That is a very concrete example of MonkeOS choosing system-level policy in native code instead of inheriting it from an imported desktop framework.

Userspace Installation Is A Deliberate VFS Construction Step

The runtime namespace is built, not passively discovered. During userspace::init(), the current tree does at least three explicit things:

install_base_userspace();
mesa::install();
webm::install_bundled_media();

The base install path then creates and populates concrete VFS nodes:

/bin/sh              -> bundled shell script
/bin/kim             -> "[native kim app]"
/usr/share/monkeos.svg -> bundled logo payload
/webmbundle          -> runtime-generated catalogue

So when the article says the runtime namespace is a product and not a mirror of the repo, this is what that means in code: the OS explicitly synthesizes the user-visible tree it wants.

WebMonke Is A Generated Asset Table, Not A Loose File Import

The WebMonke path is technical enough that it is worth spelling out with exact constants. In build.rs the bundle pipeline is currently parameterized by:

const WEBM_BUNDLE_DIR: &str = "src/userspace/webmbundle";
const WEBM_BUNDLE_MAX_EDGE: usize = 512;
const WEBM_BUNDLE_FPS: usize = 50;
const WEBM_BUNDLE_AUDIO_RATE: usize = 48_000;
const WEBM_BUNDLE_AUDIO_CHANNELS: usize = 2;

The build script scans only direct .webm children of that directory, probes their source dimensions, rescales them to a bounded edge, transcodes video to raw BGRA, transcodes audio to stereo s16le, and emits a generated Rust file in OUT_DIR/webmonke_bundle.rs. At runtime, src/userspace/webm/catalog.rs includes that file into a typed static table:

pub struct BundledWebmAsset {
  file_name: &'static str,
  width: usize,
  height: usize,
  fps: usize,
  audio_rate: usize,
  audio_channels: usize,
  frame_count: usize,
  audio_frames: usize,
  original_size: usize,
  video_bytes: &'static [u8],
  audio_bytes: &'static [u8],
}

Then install_bundled_media() creates /webmbundle in the VFS and writes a tiny metadata stub for each asset if the file does not already exist. The visible file in the OS is therefore a catalogue entry, not the original container blob. That is the concrete reason a README-only src/userspace/webmbundle directory can still be "working."

Boot Intro Media Is Also Generated, And The Numbers Matter

The boot intro path is not hand-wavy either. The current build-time constants are:

const BOOT_INTRO_W: usize = 320;
const BOOT_INTRO_H: usize = 320;
const BOOT_INTRO_FPS: usize = 50;
const BOOT_INTRO_AUDIO_RATE: usize = 48_000;
const BOOT_INTRO_AUDIO_CHANNELS: usize = 2;

Those values are not arbitrary. The current timer setup and desktop pacing work much better with a 50 FPS intro than with an awkward mismatched cadence, and pre-transcoding into raw video and PCM lets the intro play before login without dragging a full general-purpose container decoder into the early runtime path.

The Login Screen Sits On Top Of A Real Encrypted Volume Gate

The login path is more technical than a theme screen. The display manager in src/login.rs ultimately routes into the encrypted volume API in src/vfs/api.rs:

provision_encrypted_volume(username, password)
unlock_encrypted_volume(username, password)

That VFS layer is not decorative either. The current Monkefs backing model uses:

const BLOCK_SIZE: usize = 512;
const BLOCK_COUNT: usize = 1024;
const FS_MAGIC: u32 = 0x4D4F_4E4B;   // "MONK"
const FS_VERSION: u32 = 3;
const VOLUME_KDF_ITERS: u32 = 4096;
const KEY_LEN: usize = 32;

Provisioning generates a per-user salt, derives encryption and authentication keys with pbkdf2_hmac_sha256, computes a verifier, stores an encrypted volume header, and syncs the filesystem image back to the block device abstraction. Unlock performs constant-time verifier comparison, MAC validation over the encrypted payload, then applies the stream transform and finally loads the filesystem image into the live in-memory node table. So when the desktop is held behind login, that is not just UI choreography. The VFS really is locked.

The Login UI Has Concrete Resource Guards

Even the display-manager background path reflects this engineering style. In src/login.rs the built-in wallpaper load is guarded by:

const MAX_LOGIN_WALLPAPER_PIXELS: usize = 1_500_000;

That constant exists because the project already hit early-boot allocation failures while decoding large images. This is a good example of MonkeOS converting a historical panic into an explicit architectural bound.

Session Selection Is A Typed Runtime Contract

The current session layer in src/session.rs is not a stringly-typed theme selector. It uses concrete enums for both sides of the session split:

enum WindowManagerKind {
  Stag,
  Headless,
}

enum DesktopEnvironmentKind {
  MonkeDesktop,
  Console,
}

SessionSelection::normalized() is where the current compatibility matrix is enforced. Right now MonkeDesktop normalizes to Stag, and Console normalizes to Headless. The session layer also exports .mnks-style manifests through MnksManifest, which is why the article keeps describing WM and DE as separate architectural entities instead of one blurred desktop label.

Where The Tree Is Still Technically Uneven

The most honest technical summary is that MonkeOS currently has several strongly implemented vertical slices rather than one perfectly uniform runtime plane. The kernel boot path is explicit. The login-to-session handoff is explicit. The VFS encryption gate is explicit. Native desktop apps are explicit. Build-time asset normalization is explicit. What is still uneven is how those slices meet a future generalized userspace model.

That unevenness is why the article refuses to flatten everything into a fake slogan. Right now MonkeOS is best understood as a kernel-owned runtime that exposes a typed VFS, a native desktop stack, a shell, a small execution bridge, generated asset catalogues, and early compatibility work, all while still discovering where the permanent abstraction boundaries should land.

FAQ

Why not start smaller?

Because the point of MonkeOS is not to remain small. The project is meant to discover what happens when you keep pushing into desktop, storage, media, userspace, and tooling instead of stopping at “hello from long mode”.

Why keep both shell and desktop?

Because they answer different needs. The shell is the stable, inspectable control surface. The desktop is where the project learns windowing, input, graphics presentation, and app UX. MonkeOS is healthier with both.

Why not adopt Wayland right now?

Because protocol branding does not automatically fix compositor timing, dirty-region behavior, cursor independence, title metrics, or app content scaling. MonkeOS first needs a backend that feels good. Standardization can come later.

Why does the repo have README-only directories?

Because MonkeOS distinguishes between source staging directories, companion project roots, generated outputs, and runtime VFS surfaces. A directory that only contains a README.md is often a real architectural boundary whose concrete payload is optional, generated elsewhere, or not yet worth externalizing from the live runtime path.

Is kim actually implemented or just planned?

It is implemented. The live editor is native code in src/kim.rs, launched through the app manager and surfaced through the shell and desktop. The separate .kim-seed path is a companion root, not the body of the runtime editor.

Why does /webmbundle not mirror src/userspace/webmbundle exactly?

Because the source directory is an input staging area for optional .webm files, while /webmbundle is a runtime catalogue built from generated bundled assets. The runtime namespace is a product of boot and bundling logic, not a raw export of the source tree.

Why not hide all this complexity behind cleaner abstractions?

Because premature abstraction is one of the easiest ways for a young OS to start lying to itself. MonkeOS would rather let the code look a little uneven while the real boundaries are still being discovered than freeze those boundaries into a fake polished story that the runtime cannot actually honor.

Why document design choices this explicitly?

Because otherwise the repo can look contradictory. With the design choices spelled out, the contradictions mostly resolve into a system that is choosing useful realism over cleaner-looking fiction.