A bare-metal operating system where CPython 3.14 is the kernel — not a program running on an OS, but the OS itself. Python owns the machine from interrupt handlers to the interactive shell. Runs on x86_64 and arm64 (QEMU virt).
Boots directly to a >>> prompt on the serial console. An opt-in GUI desktop with a stacking compositor, PySDL2-compatible Python API, PNG/JPEG decoders, audio mixer, and five built-in apps (terminal, editor, file browser, image viewer, audio/graphics demos) is one make target away — see GUI Mode below.
Run make help at any time for the top-level target listing.
- Docker (for cross-compilation toolchain)
- QEMU (
brew install qemuon macOS) - CPython 3.14 source tree (fetched by the build)
make docker-build # build the Docker cross-compilation image once
make # cross-compile libpython + kernel, produce pythonos.isomake # rebuild kernel + ISO if sources changed# Default boot — serial REPL, no GUI window. Picks host arch automatically.
make run
# x86_64 CPU count defaults to 2; override when needed
SMP_CPUS=4 make run
# Experimental x86_64 CPython free-threading build
PYTHONOS_FREE_THREADING=1 make cleanall all
# Explicit per-arch forms when you want to be specific:
make run-x86_64
make run-arm64
# Kill a running instance
make stop
make stop-arm64Use Ctrl-A X to exit QEMU.
Opt-in graphical desktop with a stacking compositor, mouse + keyboard input, audio output, and five built-in apps. The default make run and make test paths are unchanged — they still boot serial-only with -nographic.
# Boot to framebuffer REPL inside an SDL window;
# type `pythonos_gui` at >>> to open the compositor + an app.
make run-gui
# Boot AND auto-launch the desktop in one step (host runs a small
# launcher that sends `pythonos_gui <app>` over the TCP REPL once
# the kernel comes up).
make run-desktop
PYTHONOS_DESKTOP_APP=terminal make run-desktop
PYTHONOS_DESKTOP_APP=editor make run-desktop
PYTHONOS_DESKTOP_APP=files make run-desktop
PYTHONOS_DESKTOP_APP=image_viewer make run-desktop
PYTHONOS_DESKTOP_APP=audio_tone make run-desktopInside the compositor:
- Tab / Shift-Tab cycles focus between windows.
- Click a window's title bar to drag it; click in the body to focus + raise.
- ESC typically closes the focused app and returns to the REPL.
See docs/gui.md for the full feature reference (compositor, sdl2 API surface, image decoders, audio backends, apps).
The no-GIL build is currently supported on x86_64 QEMU with SMP enabled. It
boots CPython with PY_GIL_DISABLED=1, starts AP-backed pthread workers through
_thread, and uses a small mmap shim that validates fixed mappings before
touching memory. The support boundary is still intentionally narrow: native
threads are worker dispatches onto initialized APs, not a full scheduler for
arbitrary blocking POSIX workloads.
Once the kernel is running, a multi-session Python REPL listens on TCP port 5000 inside QEMU. QEMU forwards it to your host:
# x86_64
nc localhost 5555
# arm64
nc localhost 5556Each connection gets an independent kernel shell with access to all live kernel objects. Multiple sessions can run simultaneously — this is the point: Python is the kernel, and concurrency is asyncio, not fork/exec.
PythonOS kernel shell
Python 3.14.0a0
Type 'help' for help.
Commands: ls ps pwd cd cat cp mv ftp ed sysinfo netstat
Helpers: sh() sh('cmd args') run('/path') clear()
>>> 1 + 1
2
>>> sysinfo
PythonOS
Scheduler: 3 tasks
cwd: /
>>> ls /bin
. .. ls.py ps.py pwd.py cd.py cat.py cp.py mv.py ftp.py ed.py sysinfo.py netstat.py
>>> cd /tmp
>>> pwd
/tmp
>>> sh()
$ sysinfo
PythonOS
Scheduler: 3 tasks
cwd: /tmp
$ ls
. ..
$ exit
>>> import kernel.log as log; log.info("hello from REPL")
The kernel shell offers a Unix-like command experience nested inside the Python REPL. The two modes coexist without friction.
Any bare identifier typed at >>> that is not already a Python name is looked up as /bin/<name>.py and executed automatically. No run(), no quotes, no .py extension needed:
>>> ls
>>> ls /bin
>>> ps
>>> pwd
>>> cd /tmp
>>> cat /examples/README.txt
>>> ed /tmp/notes.txt
>>> sysinfo
>>> netstat
Arguments are split on whitespace and passed to the script as argv:
>>> cp /bin/sysinfo.py /tmp/my_sysinfo.py
>>> mv /tmp/my_sysinfo.py /tmp/sysinfo_backup.py
>>> ftp get /tmp/host-file.txt
>>> ftp put /tmp/host-file.txt
| Command | Description |
|---|---|
ls [path] |
List directory (default: current working directory) |
ps |
List kernel tasks with PID, name, state |
pwd |
Print current working directory |
cd [path] |
Change directory (supports .., relative paths; default: /) |
cat FILE [...] |
Print file contents |
cp SRC DST |
Copy a file |
mv SRC DST |
Move / rename a file |
ftp get DST [PORT] |
Receive one TCP file stream into TmpFS |
ftp put SRC [HOST] [PORT] |
Send one TmpFS file over TCP |
ed [path] |
ed-style line editor |
sysinfo |
System overview |
netstat |
Network interface status |
Calling sh() with no arguments drops into an interactive shell with a $ prompt. It dispatches the same /bin/*.py commands and shares cwd state with the parent session. Type exit to return to >>>:
>>> sh()
$ ls /bin
. .. ls.py ps.py pwd.py cd.py cat.py cp.py mv.py ftp.py ed.py sysinfo.py netstat.py
$ cd /tmp
$ pwd
/tmp
$ /examples/tone.py
$ exit
>>> cwd # cwd change is visible back in Python
'/tmp'
Inside sh(), .py paths run directly, and foo.py resolves like
./foo.py from the current directory. Python files are native executables in
PythonOS.
sh('cmd args') is the single-shot form for scripted use:
>>> sh('cp /bin/sysinfo.py /tmp/backup.py')
>>> sh('ls /tmp')
>>> sh('/examples/tone.py')Any .py file placed in /bin/ becomes a shell command automatically. Scripts run inside the kernel namespace — vfs, scheduler, pci, net, OpenFlags, and print are pre-bound. Top-level await is supported so scripts can call async VFS operations directly:
# /bin/mycommand.py
# Invoked by typing: mycommand arg1 arg2
path = argv[0] if argv else cwd
entries = await vfs.readdir(path)
for e in entries:
print(e)argv is a list of string arguments. cwd is the current working directory string; a script can update it (cwd = '/new/path') and the shell propagates the change back.
Create a new command at runtime:
>>> fd = await vfs.open('/bin/hello.py', OpenFlags.WRONLY | OpenFlags.CREAT | OpenFlags.TRUNC)
>>> await vfs.write(fd, b"print('hello, ' + (argv[0] if argv else 'world'))\n")
>>> vfs.close(fd)
>>> hello
hello, world
>>> hello PythonOS
hello, PythonOSPythonOS also seeds /examples with readable Python programs that are frozen into the kernel as bytecode, so they can use normal functions, classes, async loops, and imports without depending on the limited runtime compiler:
>>> ls /examples
README.txt async_tasks.py hello_kernel.py primes.py recv_file.py send_file.py tone.py vfs_demo.py
>>> run('/examples/hello_kernel.py')
>>> run('/examples/vfs_demo.py')
>>> run('/examples/async_tasks.py')
>>> sh('/examples/primes.py 100')
>>> run('/examples/tone.py')
The smaller examples cover shell arguments, scheduler/VFS inspection, TmpFS read/write, cooperative asyncio tasks, and pure-Python computation.
The ed command is backed by kernel.ed, a line-oriented editor inspired by
py_ed. It supports append/insert/change/delete, print/number/literal print,
marks, move/copy, read/write/write-append, one-level undo, and q/Q.
The ftp command provides a small FTP-like file copy workflow over one raw TCP stream. It is not the RFC FTP protocol; it is a simple get/put tool built for the PythonOS shell and TmpFS:
# inbound to PythonOS; make run forwards host localhost:17000 to guest port 7000
>>> ftp get /tmp/inbox.txt
# host terminal:
$ nc localhost 17000 < local-file.txt
# run-arm64 forwards host localhost:17002 to guest port 7000
# outbound from PythonOS to the QEMU host at 10.0.2.2
# host terminal:
$ nc -l 7001 > from-pythonos.txt
>>> ftp put /tmp/inbox.txt
Use FILE_HOST_PORT=<port> make run if you need a different host-side
forwarded port.
The older file-transfer examples are still available as readable source at /examples/recv_file.py and /examples/send_file.py.
The two modes are additive. Anything that is a valid Python expression or statement still goes through the Python interpreter. The shell dispatch only fires for bare identifiers that are not already in the Python namespace:
>>> ls # shell dispatch → /bin/ls.py
>>> list(pci) # Python expression → PCI device list
>>> for d in pci: print(d) # Python statement → loop over PCI bus
>>> sh() # enter shell sub-REPL
$ ps # shell command
$ exit # return to >>>
>>> scheduler.ps() # back to Python
The >>> prompt is also available on the serial console (the QEMU terminal window itself). All interactive I/O goes through COM1 (x86_64) or PL011 (arm64) — no PS/2, no framebuffer required.
make test # default boot smoke (serial + TCP REPL based)
make test-gui # GUI subsystem smoke (headless screendump + audio capture)make test boots PythonOS in QEMU as a subprocess, waits for the TCP REPL to become reachable, runs a set of Python expressions through it, verifies the expected output, and exits with code 0 on pass. The x86_64 smoke test boots with two virtual CPUs by default; use SMP_CPUS=4 make test to expose more.
make test-gui runs three additional suites on x86_64 (or one on arm64):
- gui smoke — sdl2 corpus (
hello/renderer/text/image/jpeg), compositor render, mouse pipeline, pointer round-trip, serial markers - desktop smoke — boots, auto-launches
pythonos_gui bouncing_ball, screendumps, asserts pixel-exact desktop bg + title bar + window body + tile-hash golden - audio smoke — boots with
-audiodev wav,id=a, runsexamples/tone.py, verifies the captured WAV header (48 kHz / 2ch / 16-bit)
Counts at HEAD: 41 / 28 / 23 / 5 / 6 / 8 across the six suites (default x86, default arm64, x86 gui, x86 desktop, x86 audio, arm64 gui) — 111 tests total.
For the no-GIL path, run PYTHONOS_FREE_THREADING=1 SMP_CPUS=4 make test.
That smoke covers the boot-time SMP self-tests, _hal.pthread_selftest(), and
/examples/thread_demo.py, including multiple Python worker threads and timed
lock acquisition.
Most operating systems are written in C, with scripting languages bolted on top as userspace programs. PythonOS inverts that: the Python interpreter is the kernel primitive. There is no C runtime managing Python — Python manages the machine.
The philosophical bet: "everything is an object" is a better organizing principle for a kernel than "everything is a file." importlib, exec, and inspect become the OS hot-reload and introspection syscalls. The system can extend itself at runtime without rebooting.
GRUB2 (multiboot2)
└─▶ boot.asm — long mode, 4 GiB identity map, 4 MiB stack
└─▶ main.c — GDT, IDT, PIC (8259A), PIT (100 Hz), COM1 serial
├─▶ kthread.c — bootstrap-CPU native thread self-test
├─▶ smp.c — ACPI MADT discovery, AP startup, per-CPU state, native AP worker dispatch
└─▶ hal.c — CPython init, freeze frozen modules
└─▶ kernel.boot() — Python owns the machine
├─▶ PMM + VMM
├─▶ PCI enumeration + driver binding
│ ├─▶ VirtIO-net (DMA descriptor rings)
│ └─▶ Intel HDA (BDL DMA, codec)
├─▶ asyncio event loop (PIT-driven, 100 Hz)
├─▶ Network stack (ARP/IP/TCP)
├─▶ TCP REPL server (port 5000, multi-session)
├─▶ COM1 serial input driver
└─▶ Interactive kernel shell (serial + TCP)
U-Boot / QEMU direct kernel load
└─▶ boot_arm64.asm — EL1, MMU off, 4 MiB stack
└─▶ main_arm64.c — GIC v2, generic timer (100 Hz), PL011 serial
└─▶ hal.c — CPython init, freeze frozen modules
└─▶ kernel.boot() — Python owns the machine
├─▶ PMM + VMM
├─▶ VirtIO-MMIO block device (raw disk image)
├─▶ asyncio event loop (GIC timer-driven)
├─▶ PL011 serial input driver
└─▶ Interactive kernel shell (serial)
[PythonOS] INFO kernel.boot: interrupt router connected
[PythonOS] INFO kernel.boot: PMM ready — 510 MiB free
[PythonOS] INFO kernel.boot: VMM ready
[PythonOS] INFO kernel.boot: enumerating PCI bus...
[PythonOS] INFO kernel.boot: 7 PCI devices found
[PythonOS] INFO kernel.boot: tmpfs mounted at /
[PythonOS] INFO kernel.boot: event loop ready
[PythonOS] INFO kernel: no framebuffer — serial only # default boot
[PythonOS] INFO kernel: COM1 serial input ready
[PythonOS] INFO virtio-net: MAC 52:54:00:12:34:56
[PythonOS] INFO kernel: network stack starting
[PythonOS] INFO repl: TCP REPL listening on port 5000 — connect: nc localhost 5555
[PythonOS] INFO kernel: HDA sound ready
[PythonOS] INFO mixer: attached backend HDADriver
[PythonOS] INFO kernel: shell spawned — system ready
PythonOS kernel shell
Python 3.14.0
Type 'help' for kernel commands.
>>>
In make run-gui mode the framebuffer + GUI input substrate also come up:
[PythonOS] boot: framebuffer found
[PythonOS] INFO kernel: framebuffer console ready
[PythonOS] INFO kernel: GUI input ready (PS/2 kbd+mouse)
On arm64 with make run-gui-arm64 the equivalent markers are:
[PythonOS] INFO ramfb: 1024x768x32 ready
[PythonOS] INFO virtio-input: ready at 0xa003e00
[PythonOS] INFO kernel: GUI input ready (virtio-input x2)
[PythonOS] INFO virtio-snd: ready at 0xa003a00 (stream 0, 48000 Hz / 2ch / S16)
[PythonOS] INFO kernel: virtio-snd ready
src/
boot/ asm + C bootstrap: GDT, IDT, PIC, PIT, serial, framebuffer (x86_64)
boot_arm64.asm, main_arm64.c — GIC, generic timer (arm64)
hal/ hal.c — _hal Python C extension (port I/O, MMIO, interrupts, DMA)
libc/ freestanding libc: buddy allocator, string, stdio, POSIX stubs
kernel/
__init__.py boot() entry point — wires all subsystems
hal/ thin Python wrapper over _hal
interrupts/ interrupt router, @interrupt decorator, default handlers
bus/pci.py PCI enumeration (CF8/CFC), driver Protocol, PCIBus
display/ framebuffer + bitmap font console
log.py serial logging via _hal (arch-aware: COM1 / PL011)
memory/ PMM (page frame allocator), VMM (virtual address spaces)
drivers/
input/
com1.py COM1 16550A serial input (x86_64 headless)
pl011.py PL011 UART input (arm64 virt)
virtio_input.py virtio-input keyboard + mouse + tablet over MMIO (arm64)
keyboard.py PS/2 keyboard (x86; IRQ1, scancode set 1)
mouse.py PS/2 mouse (x86; IRQ12, 3-byte packet)
net/
virtio_net.py VirtIO-net driver (DMA descriptor rings)
block/
virtio_blk.py VirtIO-MMIO block driver (arm64)
display/
bochs.py bochs-display PCI driver (x86 GUI)
ramfb.py arm64 ramfb via fw_cfg
fwcfg.py fw_cfg helper
sound/
virtio_snd.py arm64 virtio-snd backend
sound/
hda.py Intel HDA driver (BDL DMA, codec configuration)
mixer.py arch-neutral PCM playback API (Mixer)
gui/ GUI subsystem — opt-in, only active under run-gui / run-desktop
input.py canonical Event + EventQueue; PS/2 + virtio-input bridges
compositor.py stacking window manager (Tab focus, drag, click-to-focus)
sdl2/ PySDL2-compatible Python API (Init, Window, Surface, Renderer,
events, sdlmixer, sdlttf — see docs/gui.md)
image/ image decoders: bmp, ppm, png (with pure-Python deflate), jpeg
net/
ethernet.py Ethernet frame encode/decode
arp.py ARP request/reply
ip.py IPv4 packet encode/decode, checksum
tcp.py TCP state machine (connect + listen/accept)
stack.py Network stack init, ARP/IP dispatch
repl_server.py Multi-session TCP REPL server (port 5000)
fs/ VFS (POSIX-like fd table, CREAT/TRUNC, async protocol) + tmpfs
ed.py ed-style line editor used by /bin/ed.py
scheduler.py asyncio task scheduler (ps, spawn)
shell.py kernel shell: Python REPL + bare-word /bin dispatch + sh() sub-REPL
apps/ built-in GUI applications (frozen, registered via apps.registry)
terminal/ Python REPL inside a CompositorWindow
editor/ ed line editor in a window
files/ arrow-key file browser
image_viewer/ BMP / PPM / PNG / JPEG viewer
demos/ bouncing_ball (graphics), audio_tone (440 Hz)
_textwin.py shared text-grid view used by terminal + editor
bin/ (seeded in tmpfs at boot — add .py files here to create new shell commands)
ls.py, ps.py, pwd.py, cd.py, cat.py, cp.py, mv.py, ftp.py, ed.py — filesystem / transfer utilities
sysinfo.py, netstat.py — system / network status
pythonos_gui.py — desktop launcher
examples/ frozen runnable demos, also seeded as readable source in /examples
hello_kernel.py, vfs_demo.py, async_tasks.py, primes.py, tone.py,
recv_file.py, send_file.py
fb_test.py, sdl_hello.py, sdl_renderer.py, sdl_text.py,
sdl_image.py (PNG corpus), sdl_jpeg.py (JPEG corpus)
asyncio/ bare-metal asyncio (no socket/selectors): Future, Task,
Queue, Event, Lock, Semaphore, sleep, wait_for, gather
tests/
smoke_test.py x86 default smoke (TCP REPL based)
smoke_test_arm64.py arm64 default smoke (PL011 serial)
gui_smoke_test.py x86 GUI smoke (sdl2 corpus + compositor + pixel checks)
gui_smoke_test_arm64.py arm64 GUI smoke (ramfb + virtio-input + screendump)
desktop_smoke_test.py end-to-end pythonos_gui auto-launch + tile-hash golden
audio_smoke_test.py WAV-capture audio pipeline check
qmp_helper.py QEMU monitor wrapper (screendump, sendkey, mouse_*)
goldens/x86_64/ checked-in tile-hash goldens (refresh: PYTHONOS_GOLDEN_REFRESH=1)
tools/
freeze_kernel.py compiles kernel + apps + examples → frozen C bytecode in the ELF
run_desktop.py boots QEMU then auto-sends `pythonos_gui` to the TCP REPL
stdlib_stubs/ bare-metal replacements for stdlib modules that assume
a POSIX host (dataclasses, functools, os, ctypes, sdl2, …)
Dockerfile Ubuntu 24.04 cross-compilation environment
setup_cpython.sh fetch, patch, and configure CPython 3.14 for bare metal
deps/
Modules.Setup.local CPython built-in module list (excludes socket/select/…)
cpython/ built libpython3.14.a + headers (generated; not in git)
cpython-src/ CPython 3.14.0 source (generated; not in git)
CPython 3.14.0 is compiled as a static library (libpython3.14.a) linked directly into the kernel ELF. Py_Initialize() runs before any Python code; from that point forward, the Python interpreter is the kernel runtime.
All kernel Python modules are frozen — compiled to bytecode and embedded in the ELF as C arrays. There is no filesystem required for import. The freeze tool (tools/freeze_kernel.py) runs at build time and produces build/frozen_kernel.c.
src/hal/hal.c implements the _hal built-in Python module, compiled into the kernel. It exposes:
| Function | Description |
|---|---|
inb/inw/inl(port) |
Port I/O reads (x86_64) |
outb/outw/outl(port, val) |
Port I/O writes (x86_64) |
mmio_read8/32(addr) |
Memory-mapped I/O reads |
mmio_write8/32(addr, val) |
Memory-mapped I/O writes |
dma_alloc(size) |
Allocate zeroed C-heap DMA buffer, return physical address |
buf_addr(bytearray) |
Return physical address of bytearray's internal buffer |
read_cr2/cr3() |
Control register reads (x86_64) |
set_interrupt_router(fn) |
Register Python interrupt dispatcher |
set_event_loop(loop) |
Register asyncio loop for interrupt-safe dispatch |
ARCH |
String constant: "x86_64" or "arm64" |
Python's garbage collector must never touch DMA buffers (device-visible memory). _hal.dma_alloc(n) allocates from the C buddy allocator (calloc) and returns an integer physical address. The GC cannot see this allocation. This is used by VirtIO-net descriptor rings, HDA BDLs, and RX packet buffers.
The asyncio/ package is a from-scratch implementation of the asyncio API — no select, no sockets, no selectors. The event loop is driven by the PIT timer on x86_64 or the GIC generic timer on arm64 (both at 100 Hz), with hardware interrupt callbacks routed through call_soon_threadsafe. Full API: Future, Task, Queue, Event, Lock, Semaphore, sleep, wait_for, gather, ensure_future.
The REPL server (kernel/net/repl_server.py) listens on TCP port 5000. Each incoming connection spawns a new asyncio task running an independent Shell instance. All sessions share the same live kernel namespace — scheduler, vfs, pci_bus, the network stack — because they are all coroutines inside the same Python process that is the kernel. This is the most direct possible demonstration that Python-as-kernel supports preemptive multitasking: multiple interactive sessions, zero threads, zero processes, one interpreter.
Several stdlib modules assume a POSIX host and break on bare metal. tools/stdlib_stubs/ contains replacements:
| Stub | Why it exists |
|---|---|
dataclasses.py |
CPython's version uses exec() to generate __init__ etc., which fails on bare metal with a spurious SyntaxError. Rewritten using closures. |
functools.py |
_CacheInfo = namedtuple(...) uses eval(). Replaced with a plain class. |
os.py |
The real os.py imports posix which expects _have_functions. Minimal stub. |
ctypes/__init__.py |
_ctypes is not compiled in (requires libffi). Minimal stub for addressof; prefer _hal.dma_alloc for DMA. |
random.py |
Minimal LCG PRNG seeded from PIT port reads. |
traceback.py |
CPython 3.14's version imports _colorize (not compiled in). Minimal format_exc. |
linecache.py |
No source files on bare metal; no-op cache. |
inspect.py, pathlib.py |
Minimal stubs used during module discovery. |
Real stdlib files that are frozen verbatim: enum, typing, operator, types, reprlib, keyword, copy, weakref, _weakrefset, contextlib, warnings, copyreg, struct, codeop, __future__, re, collections.
The CPython configuration disables everything that requires a host OS:
- No
socket,select,selectors,ssl,readline,termios - No
fork,exec,subprocess - No dynamic loading (
dlopen) - Built-in modules only:
_struct,_collections,_functools,_io,_signal,math,_warnings,_weakref,_abc,_json,_csv,_datetime,_pickle,_random,_bisect,_heapq,_operator,_stat,array,binascii,zlib,_hashlib,_sha256,_sha512,_blake2,_md5, and_hal
See deps/Modules.Setup.local for the complete list and tools/setup_cpython.sh for the configure flags.
Part 6 of an ongoing chronicle. ← Part 5: WebMux Sir Reginald von Fluffington III appears throughout. He does not endorse any of it.
It began, as many of the programmer's projects do, with a sentence that sounded completely reasonable at the time.
"The kernel," he announced to the living room at large, "should be written in Python."
Sir Reginald von Fluffington III was asleep on top of the programmer's copy of Modern Operating Systems, which he had been using as a mat for three weeks and had no intention of vacating. He did not open his eyes. He did not move. He did, however, lower one ear approximately four degrees — the feline equivalent of a raised eyebrow — before returning to the serious business of being unconscious.
The programmer took this as encouragement.
The idea was not new, exactly. Embedded Python had existed for years. MicroPython ran on microcontrollers. Plenty of people had run Python on operating systems. But running Python as the operating system — as the interrupt handler, the memory manager, the scheduler, the filesystem, the network stack — that was a different claim. The programmer called it "elegant." Sir Reginald, who had heard this word applied to seventeen previous decisions of varying quality, updated a private ledger entry he maintained under the heading "Hubris, General."
The first problem was CPython itself. The interpreter was designed to run on a POSIX host. It expected fork. It expected select. It expected a great many things that do not exist when the machine has just powered on and there is no OS beneath you. The programmer spent several weeks writing configure flags that deleted these expectations one at a time, like a careful editor removing every assumption that civilization exists. The result was libpython3.14.a: a static library containing the full Python interpreter, stripped of every syscall it had ever trusted, linked directly into the kernel ELF.
"No socket. No readline. No fork," the programmer told Sir Reginald, presenting the Modules.Setup.local like a trophy. Sir Reginald examined it by sitting on it. Then he sat on the keyboard. The programmer, interpreting this as a request for a demonstration, moved the cat, opened a terminal, and typed make run.
The kernel booted. Python initialized. The >>> prompt appeared on the serial console. Sir Reginald, who had relocated to the cable routing behind the monitor, was unavailable for comment.
What made the project genuinely strange was the interrupt model. In a normal OS, hardware interrupts are handled in C, which carefully manages processor state before dispatching to higher-level code. In PythonOS, the C bootstrap handles the raw vector, then immediately calls asyncio.get_event_loop().call_soon_threadsafe(interrupt_router, vector) — and Python takes it from there. The PIT timer fires 100 times a second. The GIC fires 100 times a second on arm64. Both route through the same Python @interrupt decorator. The scheduler is asyncio. The kernel shell is await shell.run(). The entire operating system is, at runtime, a set of coroutines.
Sir Reginald was not impressed by this. He had watched the programmer write asynchronous code before and knew where it ended: debugging asyncio.ensure_future at two in the morning while muttering about the event loop.
The arm64 port required a separate detour. QEMU's virt machine has no PCI bus — only VirtIO-MMIO, a GIC interrupt controller, and a PL011 UART. The programmer ported the timer, the serial driver, and the block device by reading ARM Architecture Reference Manual sections that Sir Reginald found structurally identical to the programmer's earlier operating systems reading in that both involved large books and produced no tuna.
The TCP REPL was the part the programmer was most pleased with. Having added a TCP stack from scratch — ARP resolution, IPv4, a basic TCP state machine with SYN/SYN-ACK/ACK, FIN handling, a TCPListener class — he wired it to the kernel shell and exposed it on port 5000. QEMU forwarded host port 5555 to guest port 5000. nc localhost 5555 connected to a live Python interpreter that was the kernel. Multiple sessions could run simultaneously. Each session shared the same live kernel objects — vfs, scheduler, the PCI bus — because they were all coroutines in the same process that happened to be the operating system.
"This," the programmer said, "proves that Python is a real multitasking kernel."
Sir Reginald walked across the keyboard. The shell printed TypeError: unsupported operand and terminated. The programmer noted this was a separate bug and filed it accordingly.
The smoke test was added last, as things tend to be when the thing being tested is a kernel that takes forty-five seconds to compile and requires QEMU to run. tests/smoke_test.py boots the ISO as a subprocess, waits for the TCP REPL to become reachable, connects, and verifies that 1 + 1 returns 2, that vfs is not None returns True, that 1 / 0 raises ZeroDivisionError, and that the kernel's scheduler and filesystem are alive and accessible from a remote TCP session. If any of these fail, make test exits non-zero. Sir Reginald has never run make test. He has, however, sat on the test output twice, which the programmer is counting as a code review.
As of this writing, PythonOS has been used in production by exactly one person, who also wrote it. Sir Reginald continues to withhold his endorsement across all 6 projects, citing "procedural concerns," "insufficient tuna," "a general atmosphere of hubris," and, now, "the fundamental unseriousness of an operating system that can be interrupted by the garbage collector."
BSD 2-Clause. See LICENSE.