What this system is: JayAI
One laptop in Michigan runs our websites, our AI agents, a radio station, a phone camera monitor and the dashboards that watch all of it. This is how it fits together, and the principles that keep it ours.
In one sentence: JayAI is a self-hosted platform where coding agents (Claude Code, Codex and Kimi) build and run small Ruby services on one machine, every service is exposed through a single tunnel, every service reports to one local dashboard, and every outside provider sits behind an adapter we can swap.
The machine
Everything runs on a single Lenovo ThinkPad X1 Extreme laptop: 16 CPU threads, 30 GiB of memory, a 4 GiB NVIDIA GTX 1650 and a 1 TB SSD, on Ubuntu. There is no cloud server and no container platform underneath the core. Each service is a plain systemd user unit running as an ordinary user, not root, and starts at boot without anyone logging in.
Nothing listens on the open network. Every service binds to localhost, and the only way in from the internet is an outbound ngrok tunnel. That one choice removes a whole class of problems: no open ports, no firewall rules to get wrong, no public IP to defend.
The orchestration layer: agents as the workforce
The point of the machine is to be a place where AI agents do real work. Three agent CLIs are installed side by side, each signed in with its own account:
- Claude Code (Anthropic), which also exports its sessions, token counts, cost and tool use to our own telemetry collector, so we can see what each piece of work cost.
- Codex CLI (OpenAI).
- Kimi Code (Moonshot AI).
Next to them sit local models served by Ollama (small Qwen and vision models on the GPU) and a local whisper.cpp speech-to-text server. The hosted agents do the heavy reasoning and writing; the local models handle the steady, private, high-volume jobs like transcribing radio or describing camera frames, where sending data out would be slow, costly or simply not ours to send.
Having three providers is deliberate. Our rules ask for at least two swappable providers for anything important, especially AI models, so we can compare them on price and quality and survive any one of them changing terms or going away. Agents share one rulebook and one repository, take ownership of a task, work in an isolated copy, and hand off with evidence; that protocol is written up in How our agents work together, and a proposal for a threaded orchestrator that dispatches work to them is on bada.im.
One rulebook for every agent
Every agent reads the same AGENTS.md before touching anything. It is published live on ruby.im, so the rules we give our agents are the same rules anyone can read. In short:
- Think before coding, keep it simple, change only what the task needs, and prove it works. These counter the usual ways coding agents go wrong: guessing, overbuilding, wandering and stopping early.
- Ruby first. Scripts, services and glue are Ruby with the standard library where possible; C only when a measurement says Ruby is the bottleneck.
- Open, portable and self-hosted. Plain files, SQL, HTTP, JSON and Markdown; every outside service behind an adapter; our data always exportable.
- Twelve-factor apps. Config from the environment, one port per app, logs to stdout, safe to restart at any time.
- Register and observe everything, and handle secrets only through the adapter.
- Earn confidence in tests. Expected results are written before the code runs, and an agent never weakens a test to get green.
Small habits back the rules up. Every public change gets a dated, one-sentence changelog entry in the same
change, written by a tiny bin/changelog script, so each site's history is readable without git.
Websites: one server, many domains
All our public sites are served by one small Rack app on Puma. It looks at the Host header of each request and
serves the matching folder, so adding a website means creating a folder with an index.html.
A file watcher notices the new folder and the tunnel picks up the domain on its own. There is no build step:
pages are plain HTML with inline CSS, and a saved file is live on the next load.
| Site | What it is |
|---|---|
| jaycorp.us | JayCorp's home, research notes and how we run things (this page). |
| ruby.im | Engineering notes for people and agents, and the published AGENTS.md. |
| aol.pm | Security notes: how systems fail and how to defend what we run. |
| bada.im | Adversarial research on models and agents. |
| michigan.im | A live signal control panel for Southeast Michigan radio. |
The server adds the shared "More sites" footer to every page as it sends it, gives every file an ETag so edits show up immediately, and keeps its own first-party visitor and scan log instead of an outside analytics service (see Visitor and scan logging). A site can also send some of its paths to an app, which is how the dynamic pieces below appear under ordinary website addresses.
Apps: things that run, not just pages
Dynamic apps live in their own folders, each with its own Gemfile, README, systemd unit and port.
Radio. Two software-defined radios, an SDRplay RSP1 and a HackRF One, are plugged into the laptop. Hand-written Ruby and small C programs split one slice of the 2 m amateur band into fifteen channels at once, detect who is talking, stream the active repeater to every listener as one shared MP3, and transcribe each transmission with the local Whisper server. The page at michigan.im/radio shows it live, with a waterfall explorer, a transcript archive, an APRS view and a status page that says plainly whether the receiver is healthy. Other presets tune NOAA weather, airport ATIS and broadcast FM. In the background the radios run a continuous spectrum survey (the HackRF from 24 MHz to 6 GHz), record a nightly amateur traffic net into a public archive at michigan.im/traffic, and retune themselves each night from what they have learned. Everything is receive-only and limited to what is lawful to receive.
Live. A phone opens a web page and streams its camera and microphone over a WebSocket. ffmpeg splits the stream into clips and frames, whisper.cpp transcribes the audio, a local vision model describes the scene, and the results flow back to the phone within seconds. When a session ends, a language model writes a summary through a small adapter whose default is the local model and whose second provider is Claude. It is private and token-protected.
Media. A self-hosted media server and its supporting tools run in Docker Compose on the same machine, outside the main repository, and are registered for monitoring like everything else.
Observability: see everything in one place
Every service, site, scheduled job and the machine itself is listed in one registry file,
observability/services.yml, with its unit, health check and source. That list drives the monitoring,
so registering a service is how it becomes visible. Telemetry uses OpenTelemetry, the open standard, into a
local stack:
apps, sites, agents ─OTLP─▶ OpenTelemetry Collector ─▶ Prometheus (metrics)
host, GPU, journald ──────▶ ─▶ Loki (logs)
health checks ────────────▶ ─▶ Tempo (traces)
Grafana reads all three
The Ruby apps send traces through the standard OpenTelemetry gems, Claude Code sends its usage and cost, and the host reports CPU, memory, disk, network and GPU, so a slow page can be told apart from a busy laptop. All of it stays on our own disk, and because it is OTLP, the backend could be swapped without touching the apps. Our rule for "done" is that a new service shows up healthy on the dashboard and an error from it can be found there.
Secrets: one door
Code never reads a key from a hard-coded path. It asks one adapter, Secrets.fetch("NAME"), which
reads the project's git-ignored .env file today and can switch to Bitwarden Secrets Manager with one
setting. Agents may list secret names but never print, log or commit a value, and they never type a person's
password: the person stores it, and the agent reads it through the adapter.
The principles, together
- Own the stack. Open source first, self-hosted when it runs well on what we have, and paid services only when they clearly earn their place.
- Never be locked in. Every outside service sits behind an adapter with a second provider ready, in formats we can take with us. Using a proprietary tool is fine; being unable to stop is not.
- Agents are teammates with rules. Several AI providers work from one shared, public rulebook, and their work is checked against evidence rather than trusted.
- Small and plain beats clever. One laptop, plain HTML, small Ruby services, systemd and one tunnel. Each piece can be read in an afternoon.
- If you can't see it, it isn't done. Every service is registered, measured and logged in one place.
The system is young and still one machine. What makes it a platform rather than a pile of scripts is that each new piece arrives the same way: a folder, a unit, a registry entry, a changelog line, and an agent following the same rules as the last one. For the operator's view, with every command and file, see the Handoff README.