This repository packages the Cilux research harness for experimenting with a kernel-aware Codex environment inside a disposable ARM64 VM.
The main experiment code lives in cilux/, while codex/ and linux/ are
checked out as submodules pinned to the versions used by the harness.
Clone with submodules:
git clone --recurse-submodules git@github.com:PandelisZ/codex-in-the-kernel.git
cd codex-in-the-kernelIf you already cloned without submodules:
git submodule update --init --recursiveTo bootstrap the repo after cloning:
make setupcilux/- the research harness itself
codex/- Git submodule pinned to OpenAI Codex
linux/- Git submodule pinned to the Linux tree used by the Cilux kernel sample
cilux/ is a disposable ARM64 Linux VM harness for experimenting with a
kernel-aware Codex environment.
The project combines:
- a custom Linux Rust sample module (
rust_cilux.ko) - a small privileged broker inside the guest (
cilux-brokerd) - a guest-side MCP server (
cilux-mcp) - stock
codex app-serverrunning inside the VM - a QEMU/HVF host harness that boots the guest, forwards the app-server port, and exposes the host repo into the guest over virtio 9p
The current guest Codex session is intentionally configured for
danger-full-access inside the VM so the in-guest agent can use both root
shell access and the curated kernel-facing MCP tools without interactive
approval prompts.
The repository now supports two parallel guest modes:
- research kernel VM
- the existing QEMU/HVF path with the custom ARM64 kernel, Alpine
initramfs,
rust_cilux.ko, and the full trace/debugfs-backed Cilux MCP surface
- the existing QEMU/HVF path with the custom ARM64 kernel, Alpine
initramfs,
- Ubuntu desktop VM
- a stock Ubuntu 24.04.x Desktop ARM64 guest intended for UTM on the Apple virtualization backend
- keeps Codex,
cilux-brokerd,cilux-mcp, andciluxctl, but on a stock kernel it only advertises the broker surfaces that actually exist cilux_healthreportsguest_mode: desktop_stock_kernelplus per-feature capability flags, and the MCP catalog is reduced tocilux_healthandcilux_system_read
The research path remains the only mode that exposes the full Cilux kernel-trace surface today.
The core question behind this project is:
Can we give a local coding agent meaningful kernel visibility and a constrained kernel control plane without moving the agent itself into kernel space?
The current design answers that by keeping Codex in guest userspace while bridging into kernel state through a narrow, auditable broker.
More concretely, the experiment is trying to validate whether this setup is useful for:
- inspecting live kernel state from an agent session
- reading structured kernel telemetry instead of scraping ad-hoc logs
- exposing explicit kernel control operations as tools
- combining normal shell access with safer structured kernel operations
- using the same agent session for both software engineering tasks and low-level systems investigation
This harness is not trying to:
- run Codex inside the kernel
- expose arbitrary broker-side shell execution
- provide a hardened production isolation boundary
- support every possible kernel debug/control surface
- replace normal kernel development workflows
This is a research harness, not a production security product.
rust_cilux.ko- records selected kernel events into a bounded ring buffer
- exposes
caps.json,state.json, andevents.ndjsonin debugfs - exposes a Generic Netlink family for trace-mask changes, buffer clears, and basic status queries
cilux-brokerd- runs as root inside the guest
- binds
/run/cilux-broker.sock - serves newline-delimited JSON RPC
- reads Cilux debugfs state
- performs constrained netlink control operations
- records an audit trail for every request
cilux-mcp- runs as an unprivileged stdio MCP server launched by Codex on demand
- exposes structured tools/resources backed by
cilux-brokerd
codex app-server- runs inside the guest on port
8765 - is authenticated with a capability token file
- is configured for
danger-full-accessin this VM
- runs inside the guest on port
- QEMU/HVF boots a custom ARM64 kernel and external initramfs.
- The host source tree is exported read-only into the guest over virtio 9p.
- The guest mounts that export at
/workspace-ro. - The guest overlays a scratch tmpfs upperdir over
/workspace-roto produce a writable/workspace. - Host tests talk to
ws://127.0.0.1:8765through QEMU port forwarding.
The broker is the most important design decision in the project.
Instead of letting the agent poke arbitrary kernel interfaces directly through a bag of shell commands, the broker gives the experiment:
- a narrow list of supported kernel operations
- a place to log and audit those operations
- a structured JSON boundary between the agent and kernel-facing code
- a way to expose kernel state as MCP tools/resources instead of raw text only
This lets the experiment compare two access styles in the same guest:
- unrestricted in-guest shell access
- explicit structured kernel operations through the broker
cilux/vm/- host-side build, fetch, assemble, and launch scripts
desktop/- Ubuntu-on-UTM guest bootstrap scripts, systemd units, and desktop Codex config
cilux/guest/- static guest utilities:
cilux-brokerdcilux-mcpciluxctl
- static guest utilities:
cilux/tests/- host-side websocket app-server checks
cilux/docs/- architecture and interface notes
The full tool/resource list below applies to the research-kernel guest. The Ubuntu desktop guest dynamically hides trace/debugfs-backed tools and resources until the custom Cilux kernel surface is present.
Inside the guest, Codex can execute normal shell commands as root in
danger-full-access mode.
That means the agent can directly inspect:
/proc/sys/sys/kernel/debug/cilux- mounted workspace state
- kernel logs via
dmesg - loaded modules via
/proc/modules
The current Cilux MCP server exposes:
cilux_healthcilux_kernel_snapshotcilux_events_tailcilux_trace_configurecilux_buffer_clearcilux_system_read
dmesgproc_modulesproc_meminfoproc_loadavgproc_uptimeproc_cpuinfoproc_interruptsproc_vmstatproc_buddyinfoproc_zoneinfo
cilux://capscilux://statecilux://eventscilux://healthcilux://system/dmesgcilux://system/proc_modulescilux://system/proc_meminfocilux://system/proc_loadavgcilux://system/proc_uptimecilux://system/proc_cpuinfocilux://system/proc_interruptscilux://system/proc_vmstatcilux://system/proc_buddyinfocilux://system/proc_zoneinfo
cilux://events/{limit}cilux://system/{selector}
The desktop path is intentionally separate from the headless QEMU harness. It does not automate the Ubuntu installer; instead it stages a reproducible guest payload from the repo and converges the installed desktop VM afterward.
Build the Ubuntu desktop payload on the host:
make desktop-payloadThat target stages:
cilux/artifacts/desktop-payload/cilux/artifacts/desktop-payload.tar.gz
The payload contains:
cilux-brokerdcilux-mcpciluxctlcodex- the desktop Codex config
- guest helper scripts
- systemd units for the UTM share, workspace bindfs remap, broker, and app server
Create an Ubuntu 24.04.x Desktop ARM64 VM in UTM using the Apple virtualization backend and enable:
- UEFI boot
- shared networking
- keyboard and pointer
- sound
- clipboard sharing
- a VirtioFS shared directory tagged
share
After Ubuntu is installed and booted, mount the shared repo once:
sudo mkdir -p /mnt/utm-share
sudo mount -t virtiofs share /mnt/utm-shareThen install the staged payload:
sudo /mnt/utm-share/cilux/vm/desktop/install.shOr use the single guest-side bootstrap command:
sudo /mnt/utm-share/cilux/vm/desktop/one-shot.shThe installer:
- targets the first non-system desktop user by default
- accepts
--user USERorCILUX_DESKTOP_USER=...to override that choice - installs
bindfs,spice-vdagent, and the staged payload - writes guest config under
/etc/cilux - installs binaries under
/opt/cilux/bin - mounts the UTM VirtioFS share at
/mnt/utm-share - remaps the shared repo to
/workspace - enables
cilux-brokerd.service,cilux-workspace.service, andcilux-codex-app-server.service
If you want the shortest host+guest sequence, use:
make desktop-readyThen, inside the Ubuntu guest:
sudo /mnt/utm-share/cilux/vm/desktop/one-shot.shThe app-server unit is gated on guest auth material. If neither
/root/.codex/auth.json, the selected desktop user's ~/.codex/auth.json, nor
/etc/cilux/openai_api_key exists, the service stays enabled for future boots
but does not start.
Run the guest-side smoke check after install:
sudo /mnt/utm-share/cilux/vm/desktop/smoke.shIt verifies:
- the VirtioFS share and
/workspaceremap are mounted cilux-brokerdis activecilux_healthreports stock-kernel desktop modecilux_system_readworks- the Codex app-server is active when guest auth exists
The repo includes a guest-facing Codex skill at:
.codex/skills/cilux-kernel-lab/SKILL.md
This skill tells the in-guest agent how to approach the kernel/broker surface:
- start with
cilux_health - inspect state with
cilux_kernel_snapshot - use
cilux_system_readfor broader kernel-adjacent context - treat
cilux_trace_configureandcilux_buffer_clearas explicit writes
The guest app-server currently supports two ways to authenticate Codex:
OPENAI_API_KEY- a copied host
~/.codex/auth.json
During initramfs assembly:
~/.codex/auth.jsonis copied into the guest as/root/.codex/auth.jsonwhen presentOPENAI_API_KEYis written into/etc/cilux/openai_api_keywhen present
The VM bootstrap accepts either one.
cilux/vm/scripts/preflight.sh currently requires:
qemu-system-aarch64clangld.lldllvm-objcopybindgencargojqcurlnpmiaslopensslcpiopython3cargo-zigbuildrustupmakeorgmakezigdocker
It also requires one of:
OPENAI_API_KEYin the environment~/.codex/auth.jsonon the host
The simplest path is:
cilux/vm/scripts/run-e2e.shThat script runs:
preflight.shbuild-guest.shbuild-kernel.shassemble-initramfs.shlaunch.shwait-ready.shpython3 cilux/tests/app_server_e2e.py
If you want to drive the harness manually:
cilux/vm/scripts/preflight.sh
cilux/vm/scripts/build-guest.sh
cilux/vm/scripts/build-kernel.sh
cilux/vm/scripts/assemble-initramfs.sh
cilux/vm/scripts/launch.sh
cilux/vm/scripts/wait-ready.sh
python3 cilux/tests/app_server_e2e.pyStop the VM with:
cilux/vm/scripts/stop.shOnce the VM is up and wait-ready.sh has succeeded, you can attach directly to
the running guest Codex instance with the TUI client at:
cilux/vm/scripts/tui-client.py
This client talks to the live guest codex app-server over websocket using the
guest capability token written during initramfs assembly. By default it uses:
- websocket URL:
ws://127.0.0.1:8765 - token file:
cilux/artifacts/rootfs/etc/cilux/ws-token - guest cwd:
/workspace - approval policy:
never - sandbox:
danger-full-access
python3 cilux/vm/scripts/tui-client.pyInside the TUI:
- type a prompt and press Enter to send a turn
/newstarts a fresh thread/clearclears the local transcript view/quitexits
The client shows:
- streamed assistant text
- MCP startup events
- MCP tool start/completion events
- shell command execution start/completion events
- command output captured from the guest Codex session
If you want a quick non-interactive check that the guest instance is reachable:
python3 cilux/vm/scripts/tui-client.py --prompt 'Run `whoami` and answer with only the exact output.'This prints a plain transcript to stdout and is useful for smoke tests or simple scripting.
By default the harness writes:
- kernel build output:
cilux/artifacts/build/linux-arm64
- rootfs staging:
cilux/artifacts/rootfs
- initramfs:
cilux/artifacts/initramfs.cpio.gz
- downloaded dependencies:
cilux/artifacts/downloads
- runtime logs:
cilux/run/serial.logcilux/run/qemu.log
The current host launch path uses:
qemu-system-aarch64-machine virt,accel=hvf-cpu max-smp 4-m 4096- forwarded guest port
8765 - virtio-net
- virtio 9p host export
- serial log captured to
cilux/run/serial.log
The guest kernel and initramfs are loaded explicitly with:
-kernel <build>/arch/arm64/boot/Image-initrd <artifacts>/initramfs.cpio.gz
The websocket e2e currently checks three main behaviors:
- Positive tool use
- Codex starts a thread in
danger-full-access - uses
cilux_kernel_snapshot - uses
cilux_events_tail - returns a coherent three-line summary
- Negative write failure surfacing
- Codex calls
cilux_trace_configurewith an invalid mask - the exact failure is surfaced back to the user
- Health plus curated system reads
- Codex uses
cilux_health - Codex uses
cilux_system_readwithproc_modules - confirms
debugfs_ready - confirms
netlink_ready - confirms
rust_ciluxappears in/proc/modules
This is not just a config flag on paper.
In the running guest, Codex was observed issuing real shell commands as root. For example, a direct probe executed:
/bin/sh -lc whoami
and returned:
root
That means in-guest Codex can use:
- direct shell access
- MCP tools/resources
- both within the same session
The host repo is now mounted into the guest as:
src on /workspace-ro type 9p (ro,relatime,access=client,trans=virtio)
overlay on /workspace type overlay (rw,relatime,lowerdir=/workspace-ro,upperdir=/scratch/upper,workdir=/scratch/work,uuid=on)
That is a major result because earlier iterations were falling back to the
rootfs copy of /workspace.
The guest currently sees:
/root/.codex/auth.json
when the host has ~/.codex/auth.json.
That makes the guest app-server viable without forcing API-key-only auth.
The new MCP surface now supports:
- broker readiness
- Cilux snapshot/state reads
- ring-buffer tail reads
- curated kernel-adjacent reads from
/procanddmesg
In practice, this let the agent read:
proc_modulesproc_interruptsproc_vmstat
through structured tool calls rather than shell-only scraping.
The write surface is still intentionally small, but it is now operational:
cilux_buffer_clearcilux_trace_configure
In live experiments:
cilux_buffer_clearreturnedok: truecilux_events_tailshowed an empty list immediately afterwardcilux_trace_configuresuccessfully appliedtrace_mask = 15
This is probably the most important research result so far.
The guest agent can now do both of these in the same thread:
- raw shell inspection as
root - structured broker-mediated kernel reads/writes through MCP
That is a much stronger research setup than either of these alone:
- shell-only access
- tool-only access
Even though the guest session is full access, the broker is still useful because it keeps the kernel-facing surface explicit.
Today it still constrains:
- what kernel state is exposed as first-class tools
- what writes are treated as supported control operations
- how those operations are logged and audited
The current live status after the latest iterations is:
/readyzreturns200 OKpython3 cilux/tests/app_server_e2e.pypasses- the guest app-server is reachable at
ws://127.0.0.1:8765
These were the main issues uncovered and fixed while bringing the harness up:
- MCP tool calls were stalling on approvals until the guest thread and guest
Codex config were moved to
danger-full-access. - The 9p workspace path was not actually available until the kernel build was
forced to keep:
NETWORK_FILESYSTEMS9P_FSTMPFSTTY- PL011 console support
- The overlay workspace setup initially failed because
/scratch/upperand/scratch/workwere created before mounting tmpfs on/scratch. - Recursive
chown -R /workspacein guest init was walking the mounted repo and causing startup problems. - Packaging
.kofiles fromlinux/samples/rust/*.kowas unsafe because those artifacts could be stale. - The correct reliable packaging source is now:
cilux/artifacts/build/linux-arm64/samples/rust/*.ko
The guest still logs:
rust_cilux: cilux: trace init unavailable (errno -38), continuing without live trace subscriptions
This means the current kernel module is still not fully wired up for the trace path the experiment wants.
The rest of the broker/tool surface still works, but this is the clearest next kernel-side target.
Right now the broker can mutate:
- the trace mask
- the event ring buffer
That is by design, but if the research goal shifts toward deeper kernel control, the next step is to deliberately extend the broker API rather than relying only on raw shell access.
The guest Codex session is deliberately full access. That is useful for research, but it is not a claim of production-hard isolation.
- Expand broker-backed reads for more kernel diagnostics under
/proc,/sys, or Cilux-specific debugfs projections. - Add more structured kernel writes only when they can be justified as explicit, auditable operations.
- Investigate the
trace init unavailable (errno -38)path inrust_cilux.ko. - Compare what the agent can do faster or more reliably with shell access versus structured MCP tools.
- Add more end-to-end assertions that combine shell, MCP reads, and MCP writes in the same session.
Today the research harness successfully demonstrates:
- a full-access in-guest Codex session running as root
- a working 9p-mounted host workspace with writable overlay
- injected guest auth material for Codex
- a structured kernel-facing MCP surface for reads and limited writes
- passing websocket app-server end-to-end tests
That is enough to make the environment genuinely useful as a kernel-aware agent research sandbox, even though the trace-init path is still incomplete.