What a year eh?

For research purposes, over the last couple of months I’ve started using LLMs heavily for throwaway code. My main goal was to train myself to write good prompts and find what works (and what doesn’t) with different agents—since that’s going to be my job from now on, after all.

My setup was very similar for all of these projects: all were made using cagent, either with a hand-crafted agent or by simply running cagent run --model ....

Most of the agents only had access to the filesystem and the shell. I’ve found that newer LLMs don’t really need much else. In the GitHub clone, once I had the issues feature and the CLI working, I would ask the agent to plan its steps, create issues for everything using the CLI, and then implement the feature I asked for. There were zero MCPs involved. MCP as a spec is great, but the implementations out there are almost all pretty bad.

I’ve mostly used 3 models:

These models are great for everyday work and can do a lot of things very well. However, they all have issues. Opus, for example, would sometimes end up very confused about the codebase, miss things, and end up in a “But wait!” loop when the feature is big or the bug is nasty. GPT is just very slow and almost always ends up in a strange state where it tells me to Write "do it now" if you want me to do this—very strange. Gemini can be dumb easily and struggles to follow instructions.

When I started doing this for fun I did not expect much, but I was proven wrong quickly: the newest models are genuinely useful tools for today’s developers.

Below I’m showing some of the projects I did lately. These include a Linux-like kernel, a GitHub clone, a markdown editor, a GameBoy Color emulator, a container runtime, etc. I got way better results if I knew what I was talking about; I was able to give the agent keywords and provide semi-detailed (I’m lazy) instructions on how I wanted it implemented. In cases where I had no idea what I was talking about (GameBoy emulator), we would struggle for a bit because all I could say was pretty much “It doesn’t work”, but we did manage to make something that works in the end, it was a struggle though.

Without further ado and in no specific order, here is the list of some of the vibe-coded projects.

ARM64 kernel

This was a wild and fun one. With the agent, I managed to create a working kernel that was able to boot and run a busybox shell.

Features include:

Once all of that was implemented, I managed to cross-compile a small subset of busybox and run it as the init process of the system.

I plan to slow down LLM usage on this one and continue playing with it manually; I’ve already started to implement a very simple proc filesystem. Switching from LLM-based to finger-based coding is quite interesting. Things that I know the LLM could make in a matter of minutes can take me hours, but it’s all cool to have a working starting playground.

  1; make run-disk
  2
  3Starting QEMU with VirtIO disks... (Press Ctrl+A then X to exit)
  4vda = tarfs disk (2MB)
  5vdb = FAT32 disk (4MB)
  6=================================================================
  7
  8---
  9
 10| |/ / _ \| \ | | |
 11| ' /| |_) | \| | |
 12| . \| \_ <| |\ | |**_
 13|_|\_\_| \_\_| \_|\_\_\_**|
 14
 15# Minimal ARM64 Kernel v0.8
 16
 17[BOOT] Kernel loaded successfully
 18[BOOT] DTB not passed in x0, scanning memory...
 19[FDT] Scanning for DTB in memory...
 20[FDT] No DTB found in memory scan
 21[BOOT] DTB pointer: (nil)
 22[IRQ] GIC initialized
 23[IRQ] Exception vectors installed at 0xffff000040081000
 24[TIMER] Counter frequency: 62500000 Hz
 25[TIMER] Tick interval: 625000 counts (100 Hz)
 26[IRQ] Enabled IRQ 30 with priority 128
 27[TIMER] Timer started, 100 ticks/second
 28[PMM] Initializing physical memory manager
 29[PMM] Stack top at: VA 0xffff000040133000 (PA 0x40133000)
 30[PMM] Managed region: PA 0x40133000 - 0x48080000 (127 MB)
 31[PMM] Total pages: 32768 (128 MB)
 32[PMM] Bitmap size: 4096 bytes
 33[PMM] Bitmap at: VA 0xffff000040133000 (PA 0x40133000 - 0x40134000)
 34[PMM] First free page: PA 0x40134000 (index 180)
 35[PMM] Free pages: 32588 (127 MB)
 36[PMM] Used pages: 180 (kernel + bitmap)
 37[PMM] Physical memory manager initialized!
 38[KMALLOC] Initializing kernel heap
 39[KMALLOC] Heap at 0xffff000040134000 - 0xffff000040174000 (256 KB)
 40[KMALLOC] Kernel heap initialized!
 41[MMU] Running at virtual address 0xffff0000400873f0
 42[MMU] Kernel mapped: VA 0xffff000040080000 -> PA 0x40080000
 43[MMU] Device memory: VA 0xffff000009000000 -> PA 0x9000000
 44[MMU] Higher-half kernel initialized!
 45[SYSCALL] System call interface initialized
 46[SYSCALL] Supported: exit, read, write, brk, mmap, munmap, etc.
 47[TTY] Console initialized (80x24, 115200 baud)
 48[VFS] Initializing virtual filesystem...
 49[CHARDEV] Initializing character device drivers...
 50[VFS] Registered chrdev major 1: mem
 51[VFS] Registered chrdev major 5: tty
 52[CHARDEV] Character device drivers initialized
 53[RAMFS] RAM filesystem initialized
 54[RAMFS] Mounted ramfs filesystem
 55[VFS] Mounted ramfs as root filesystem
 56[DEVFS] Device filesystem initialized
 57[DEVFS] Created /dev/console (major 5, minor 1)
 58[DEVFS] Created /dev/tty (major 5, minor 0)
 59[DEVFS] Created /dev/null (major 1, minor 3)
 60[DEVFS] Created /dev/zero (major 1, minor 5)
 61[DEVFS] Created /dev/full (major 1, minor 7)
 62[VFS] Created /dev with device nodes
 63[VFS] Created /tmp directory
 64[PROCFS] procfs initialized
 65[VFS] Created /proc directory
 66[VFS] Virtual filesystem initialized
 67[BLKDEV] Block device subsystem initialized
 68[VIRTIO-BLK] Scanning for VirtIO block devices...
 69[VIRTIO-BLK] Found block device at 0xffff00000a003c00 (version 2)
 70[BLKDEV] Registered: /dev/vda (major 254, minor 0)
 71[VIRTIO-BLK] vda: 2 MB (4096 sectors, 512 byte blocks)
 72[VIRTIO-BLK] Found block device at 0xffff00000a003e00 (version 2)
 73[BLKDEV] Registered: /dev/vdb (major 254, minor 1)
 74[VIRTIO-BLK] vdb: 4 MB (8192 sectors, 512 byte blocks)
 75[VIRTIO-BLK] Found 2 block device(s)
 76[TARFS] TAR filesystem initialized
 77[FAT32] FAT32 filesystem driver initialized
 78[DEVFS] Created /dev/vda (block device, major 254, minor 0)
 79[DEVFS] Created /dev/vdb (block device, major 254, minor 1)
 80[BOOT] Mounting tarfs from /dev/vda...
 81[VFS] Mounting /dev/vda on /mnt (type: tarfs)
 82[TARFS] Parsed 30 entries
 83[TARFS] Mounted vda (2097152 bytes)
 84[VFS] Mounted /dev/vda on /mnt successfully
 85[BOOT] Mounted tarfs on /mnt
 86[BOOT] Mounting FAT32 from /dev/vdb...
 87[VFS] Mounting /dev/vdb on /fat (type: fat32)
 88[FAT32] Mounting filesystem from vdb
 89[FAT32] Volume: KRNLDATA, ID: 12345678
 90[FAT32] 512 bytes/sector, 1 sectors/cluster
 91[FAT32] 8032 total clusters, root at cluster 2
 92[FAT32] Filesystem mounted successfully
 93[VFS] Mounted /dev/vdb on /fat successfully
 94[BOOT] Mounted FAT32 on /fat
 95[TASK] Task subsystem initialized
 96[TASK] Initial task: PID 0 (kernel)
 97[BOOT] Executing /mnt/bin/init...
 98[USER] Loading ELF executable 'init' (88576 bytes)
 99[ELF] Loading ELF: entry=0x100000, 1 program headers
100[ELF] Segment: vaddr=0x100000-0x104364 filesz=16076 memsz=17252 flags=RWX
101[ELF] Load complete, entry point: 0x100000
102[TASK] Created task: PID 1 (init), entry=0xffff000040084e40, stack=0xffff000040152f18-0xffff000040153f10
103[USER] Created ELF process: PID 1, entry=0x100000, brk=0x105000
104[BOOT] Started init as PID 1
105[BOOT] Enabling interrupts...
106[BOOT] Interrupts enabled!
107[BOOT] Scheduler enabled!
108
109[BOOT] Entering idle loop (task 0)
110[USER] Entering user mode: entry=0x100000, stack=0x7fffffe0
111
112=================================
113KRNL Init Process (PID 1)
114=================================
115
116[init] Testing FAT32 file creation...
117[init] Created /fat/testfile successfully!
118[init] Running readdir_test:
119[TASK] Created task: PID 2 (init), entry=0xffff000040084eb0, stack=0xffff00004013d718-0xffff000040141710
120[FORK] Created child process 2 (parent 1)
121[EXECVE] Failed to open '/mnt/bin/readdir_test': error 2
122[SYSCALL] Task 2 (init) exiting with status 127
123[TASK] Task 2 (init) exiting with status 127
124[TASK] Destroyed task: PID 2 (init)
125[init] Running ls /mnt test:
126[TASK] Created task: PID 3 (init), entry=0xffff000040084eb0, stack=0xffff00004013e390-0xffff000040142390
127[FORK] Created child process 3 (parent 1)
128[ELF] Loading ELF: entry=0x400000, 2 program headers
129[ELF] Segment: vaddr=0x400000-0x427100 filesz=160000 memsz=160000 flags=R-X
130[ELF] Segment: vaddr=0x438000-0x438f90 filesz=296 memsz=3984 flags=RW-
131[ELF] Load complete, entry point: 0x400000
132[EXECVE] Loaded 'ls' at entry=0x400000, sp=0x7ffffe80
133/mnt/bin/ls
134
135/mnt:
136hello.txt etc sbin bin
137[SYSCALL] Task 3 (ls) exiting with status 0
138[TASK] Task 3 (ls) exiting with status 0
139[TASK] Destroyed task: PID 3 (ls)
140[init] BusyBox found, will use ash shell
141[init] Starting BusyBox ash shell...
142
143BusyBox v1.36.1 (2025-12-17 21:48:14 CET) built-in shell (ash)
144
145krnl$ ls -l /
146ls: ls
147ls: -l/
148/:
149fat bin mnt proc tmp dev

Lung language

I also made a language (or rather, the LLM did—I just told it my wishes). It looks something like this:

package main

import "fmt"
import "./other.lung"
import "./sub/toto.lung"

def main(): void {
    const message: string = "Hello, Lung!"
    fmt.println(message)

    let x: int = 10
    let y: int = 20
    x += y
    fmt.println("x + y =", x)

    let arr: [int] = [1, 2, 3, 4, 5]
    fmt.println("Array:", arr)
    fmt.println("Length:", len(arr))

    if x > 25 {
        fmt.println("x is greater than 25")
    } else {
        fmt.println("x is not greater than 25")
    }

    let i: int = 0
    while i < 3 {
        fmt.println("Loop iteration:", i)
        i += 1
    }

    fmt.println("my function is", my_function())
    fmt.println("sub: ", sub.my_sub_func())
}

The implementation happened in multiple phases. First, I had a working parser, lexer, and stack-based interpreter. Then came the modules that you can import. Finally, I decided I wanted to have even more fun and ended up compiling binaries using cranelift. Of course, I didn’t stop there and ended up with the ability to compile and link to other dynamic libraries. Here’s, for example, a mini-game in lung with raylib:

package main

// Static raylib FFI sanity check:
// - white background
// - two rectangles
// - some text

// --- Minimal raylib FFI surface ---

extern "C" def InitWindow(width: int32, height: int32, title: *u8): void
extern "C" def CloseWindow(): void
extern "C" def WindowShouldClose(): bool

extern "C" def BeginDrawing(): void
extern "C" def EndDrawing(): void

@repr(C)
struct Color { r: u8, g: u8, b: u8, a: u8 }

extern "C" def ClearBackground(color: Color): void
extern "C" def SetTargetFPS(fps: int32): void

extern "C" def DrawRectangle(x: int32, y: int32, w: int32, h: int32, color: Color): void
extern "C" def DrawText(text: *u8, x: int32, y: int32, fontSize: int32, color: Color): void

// Run:
//   /tmp/lung_raygame_test

def main(): int32 {
  InitWindow(800, 450, cstr("Lung + raylib (FFI)"))
  SetTargetFPS(60)

  // fixed positions
  let px: int32 = 100
  let py: int32 = 100
  let ps: int32 = 32

  let cx: int32 = 400
  let cy: int32 = 200
  let cs: int32 = 14

  while WindowShouldClose() == false {
    BeginDrawing()
    ClearBackground(Color { r: 245, g: 245, b: 245, a: 255 })

    DrawRectangle(px, py, ps, ps, Color { r: 0, g: 255, b: 0, a: 255 })
    DrawRectangle(cx, cy, cs, cs, Color { r: 255, g: 215, b: 0, a: 255 })

    DrawText(cstr("Collect the coin!"), 16, 16, 20, Color { r: 0, g: 0, b: 130, a: 255 })
    DrawText(cstr("Score:"), 16, 44, 20, Color { r: 0, g: 0, b: 0, a: 255 })

    EndDrawing()
  }

  CloseWindow()
  return 0
}

It’s very far from perfect, but I did learn a bunch because I asked the LLM way too many questions about different parts of its implementation.

Container runtime

I work at Docker, so of course I had to try making a container runtime. This time in Rust. The code for this one is available on my GitHub. The architecture is inspired by containerd but, y’know, completely generated by an LLM.

The project contains a CLI and a daemon that talk to each other over gRPC. The daemon runs natively on Linux or macOS. On Linux, it uses runc directly to create and run containers. On macOS, each container runs inside a micro-VM thanks to libkrun.

The neat part of this experiment was the moment I decided I wanted faster networking in the libkrun case. After a lot of back and forth and multiple model switches, I managed to have a user-space networking stack that was ~20 times faster than what I had at the beginning. Don’t ask me how it works, but it works.

Docker TUI

Speaking of Docker, here’s a neat little TUI for your Docker engine.

ratata containers

ratata images

ratata volumes

ratata networks

Markdown editor

Of all the projects listed here, this one is the most useful. And I even use it daily now. It’s a simple and beautiful markdown editor. From the readme:

I made this just for me, there are plenty of other markdown editors, I’m pretty sure others would be better for you and will have less bugs.

Typr has one feature I could not find in any other: the ability to freely rearrange the notes in the sidebar. This is literally why typr exists.

I actually generated this twice, with the same agent and model. In the first try, I ended up with a big HTML file with JavaScript and CSS shoved inside; it was a huge mess. After a reset I retried, and this time after a couple of minutes I had a neat little Electron project. To my surprise, it was working really well and there wasn’t that much code. Basically, I learned of the existence of the excellent tiptap library.

Raytracer

I guess I was bored and thinking about my university days and how I spent months back in the day implementing a raytracer in Java. These days you can get one in less than an hour.

raye

GameBoy emulator

Why not, right?

game boy color

game boy color

game boy color

GitHub clone

Another one that goes into the “why not” category. I made a small GitHub clone, but without the social part. I’m kinda planning on cleaning up the code, adding some more tests, and then just deploying this on a server at home so that my code has a second place to call home. I’m very happy about the CI that came out of it; it’s YAML-based like GitHub Actions, but only uses images, and you can also run it locally with no fuss. The project has a server that listens to git events, syncs its own database, and runs CI when needed. There’s a nice UI for it and also a CLI.

I was particularly impressed when I decided I didn’t want my server to be Echo-based anymore, but wanted to use ConnectRPC instead. The agent was able to—one-shot—analyze the existing code, generate protobuf files, and convert the server, CLI, and UI to the new library. If I remember correctly, the whole thing took less than an hour and worked on the first try. Truly impressive.

Repository view geet geet

Issues, with epics geet

Pull requests geet

GitHub actions inspired CI geet geet

Commits with diff views geet geet

Sessions share for cagent

This was an easy and fun one. I pointed the agent to cagent’s repository and asked it to add a “Share” command that would send the current session to a remote server. I then asked the agent to make a React/Tailwind/shadcn/ui frontend that is capable of showing the session. If memory serves, less than 10 minutes later I had everything working and was able to share my sessions from cagent to a server deployed to Fly.io.

shagent

shagent