ORBIT: Why I Built My Own AI Agent Control Plane
A few months ago I realized the way I was working with AI agents had quietly turned into a mess of browser tabs, terminal windows, and lost context. Each agent session was a tiny island. Tasks didn’t queue. Recurring work didn’t recur. Nothing remembered anything between sessions unless I copy-pasted it forward. The tooling treated AI like a chat product. I needed it to behave like an operating system.
So I built one. ORBIT — Orchestration Runtime for Browser-Interfaced Terminals — is a self-hosted Go binary that runs on a small Hetzner VPS and gives me a single browser-based control plane for AI work: terminal sessions that survive deploys, a kanban board where tasks execute themselves, cron schedules that materialize as work items, and a git-backed vault for persistent memory.
This isn’t a product. It’s a personal tool. But the architectural ideas in it are reusable, and a few of them are worth writing down.
The Problem
If you’ve spent serious time working with AI agents — Claude Code, Cursor, Aider, anything in that family — you’ve probably hit the same friction I did:
Sessions don’t persist. You close the laptop, the terminal dies, the context evaporates. You re-establish it next time, badly, from memory.
Work doesn’t queue. You can’t say “run this overnight” or “do this when I’m at lunch.” There’s no task system. The agent is either running right now in your terminal or it isn’t running at all.
Recurring work isn’t recurring. I run a weekly newsletter that requires research, drafting, and scheduling. Every week I was manually firing the same agent prompts. There was no cron-shaped layer above the agent.
Memory leaks across projects. I have a personal vault, a separate vault for theological writing, side projects of various shapes. Each needs its own context. Each needs to not leak into the others. I had no clean way to scope an agent to one workspace.
The browser is everywhere; my terminal is on one machine. I want to start a session at my desk, check on it from my phone, finish it on a laptop. SSH gets you part of the way. It does not get you all of the way.
What I wanted was a control plane: one URL, one login, all the agent work happening behind it, accessible from anywhere, persistent across everything.
The Shape of ORBIT
The whole system is a single Go binary that embeds a Svelte 5 SPA via go:embed. SQLite for state. Caddy in front for HTTPS. Systemd for lifecycle. tmux for the terminal layer.
Browser (Svelte SPA)
├── REST API (/api/*) ──→ Go HTTP handlers ──→ SQLite
└── WebSocket (/api/terminal/ws) ──→ Go proxy ──→ PTY ──→ tmux ──→ shell
The deploy story is “scp the binary, restart the service.” No container orchestration, no separate frontend deploy, no build pipeline beyond make build. For a single-user system this is dramatically simpler than the alternatives, and the cost — you do not get a CDN-served frontend — is irrelevant when there’s exactly one user.
The pattern of compiling a frontend into a Go binary isn’t novel — Hugo, Caddy, and a hundred other projects do it — but it’s worth re-stating for anyone building a personal tool: the right deploy is the one you can do at midnight without thinking. Two artifacts is one artifact too many.
tmux as the Persistence Layer
The single most important design decision in ORBIT is that terminal sessions live inside tmux, not inside the Go process.
When you click “new session” in the UI, ORBIT spawns a tmux session with a deterministic name and stores that name in SQLite. The browser opens a WebSocket. The Go server attaches a PTY to tmux attach -t <name> and proxies bytes between the WebSocket and the PTY. Resize messages travel over the same WebSocket as JSON.
The payoff is enormous and easy to miss until you’ve felt it:
- Deploys don’t kill sessions. I can rebuild and restart the ORBIT service while an agent is mid-run, and when the binary comes back up the tmux session is still there, the agent is still working, and the browser reconnects to a live conversation.
- Network blips don’t kill sessions. Close the laptop, walk to a coffee shop, reopen — the session is still there.
- The terminal layer has no special framework. It’s the same
tmuxthat’s been on every Unix box for twenty years. The Go side is a dumb proxy.
I tried to write the persistence layer myself, briefly. It was a bad idea. tmux already solves session persistence. Use the boring tool that works.
The Kanban Board Is a Task Queue
The second key idea: the task system is a kanban board where the cards execute themselves.
Each task in ORBIT has:
- A
description— the prompt the agent will run - A
status— backlog, in_progress, or done - An
actionableflag — whether the task should auto-execute when moved to in_progress
When I drag a card with actionable=true from backlog to in_progress, ORBIT spawns a fresh git worktree, opens a new tmux session inside it, and runs the agent with the task’s description as the prompt. When the agent exits, ORBIT commits the changes, runs tests, merges to main if tests pass, and cleans up the worktree.
Kanban as a UI for queued AI work is, as far as I can tell, just the right shape. You can see what’s queued, what’s running, what’s done. You can drag things around to re-prioritize. You can write tasks while you’re not at your desk and they’ll just sit in backlog until you’re ready to fire them. The same database row that drives the kanban card is the unit of work the system schedules. There is no separate task abstraction.
This collapses two things that are normally separate in agent tooling — the “to-do list” the human sees and the “job queue” the runtime sees — into one. Write the description, drag to in_progress, work happens.
Schedules Materialize as Tasks
Recurring work was the third pain point. The pattern that worked: a cron-like schedules table where each row points to a prompt. When the cron fires, ORBIT creates a kanban task with that prompt and (depending on configuration) marks it actionable.
Take the weekly newsletter as a concrete example. It ships every Wednesday, which means research has to be done Tuesday morning so I can draft on Tuesday afternoon and queue the issue Wednesday morning. In ORBIT, that isn’t a special-case automation. It’s a single row in the schedules table pointing at a “do this week’s industry research” prompt. Tuesday morning the cron fires, the row inserts a kanban card, the card auto-runs because it’s marked actionable, and by afternoon there’s a research file in the vault and a finished card on the board for me to review.
The point is the schedule layer doesn’t need a runtime of its own. It just needs to know how to insert a row. The kanban is already a runtime.
The Vault Is Memory
ORBIT’s other half is the vault: a git-backed directory of markdown that the system can read and write through both the API and the agent sessions running inside it.
This is where ORBIT’s memory lives — session journals, project context, knowledge pages, skill definitions. SQLite stores control-plane state (sessions, tasks, schedules, users). Everything content-shaped lives in the vault as plain markdown, version-controlled by git, syncable to GitHub via a 5-minute cron.
The split matters. SQLite is right for the things you want fast queries and joins on — task status, session lifecycle, user auth. Git-backed markdown is right for the things you want to read on a beach without ORBIT running, and have a real history of, and edit by hand when needed. Treating them as separate stores with separate jobs has held up well.
The agent sessions read and write the vault directly. When Claude wants to remember something across sessions, it writes a memory file. When I want to brief a new session, I point the agent at the top-level context file and the latest session journal in the vault, and it picks up the thread. The vault is the substrate; ORBIT is the orchestration.
Dogfooding
ORBIT is developed inside ORBIT. I’m writing this post in an ORBIT terminal session right now. When I want to add a feature, I write a task on the kanban board with a prompt describing the change, drag it to in_progress, and an agent does the work in a worktree. ORBIT itself ends up testing, merging, and deploying.
This isn’t a gimmick. It’s the validation loop. Every rough edge in the agent-runtime experience hits me twice — once as a user, once as the developer using ORBIT to build ORBIT. Things that would be tolerable for a tool I used occasionally become unacceptable when ORBIT is the lens through which I do most of my building. That pressure has been the single most useful design force on the project.
The Work Boundary
One thing ORBIT explicitly does not cover: my day job. I work for a company with a serious security posture, and self-hosted personal infrastructure is — rightly — not where work data belongs. So ORBIT runs my personal projects, my newsletter, my writing, and my side work. The day job lives on company-issued tooling, where it should.
The honest consequence is that I do not have one system that tracks everything I’m tracking. The personal half lives here; the work half lives elsewhere; the bridge between them is whatever I keep in my head. I haven’t found a clean way around this constraint, and I’m not sure there is one. It’s the largest unresolved tension in how I work.
What’s Still Rough
A few honest acknowledgments:
Single-user. ORBIT has auth, but it’s built around one operator. The data model assumes one human at the steering wheel. Multi-tenant is a different system, and I’m not building it.
Observability is thin. I have logs (journalctl -u orbit -f) and that’s about it. No metrics, no tracing. For a system this size that’s been fine, but if I added complexity it would bite.
The schedule UI is rough. Editing cron expressions through a web form is never going to feel great. I keep meaning to add a “translate plain English to cron” helper. (I did, eventually, with another LLM call. It’s still rough.)
Recovery edges. Sessions that were mid-streaming an LLM response when ORBIT restarts are messier than I’d like. I have logic to re-attach to in-flight chat turns and resume orphaned tasks, but it’s the part of the codebase I trust the least.
Test coverage on the frontend is zero. I made a deliberate choice not to write Svelte tests because the surface area changes too fast and the cost-benefit was wrong for one user. I would not make that choice for a team.
What I’d Tell Someone Building Their Own
If you’re tempted to build something like this — and I think the temptation is going to spread as more people work seriously with AI agents — three suggestions:
-
Lean on boring infrastructure. tmux, SQLite, systemd, Caddy, git. None of these are exciting. All of them work. The exciting infrastructure for this space hasn’t stabilized yet, and you don’t want to be debugging two unstable layers at once.
-
Pick a unit of work and build everything around it. For me it was the kanban task. Tasks are what the user creates, what the schedules emit, what the agents execute, what the worktrees branch from, what gets committed at the end. One concept, everywhere. If you find yourself with two task-shaped abstractions, collapse them.
-
The control plane and the content store want to be different things. SQLite for state, git-backed markdown for content. Don’t try to put your memory layer in the database, and don’t try to put your task queue in markdown. The natures of the data are different.
Bottom Line
ORBIT is a fairly small system — about 23,000 lines of Go and Svelte, plus another 9,000 lines of tests, deployable to a $20/month VPS, runnable on a Raspberry Pi if you wanted. It’s not a product, it doesn’t have a marketing site, and I’m not selling it. What it is, is the shape I think a personal AI workstation wants to take in 2026: a single binary, a browser UI, persistent terminal sessions, a kanban that doubles as a queue, a cron layer that emits work into the queue, and a git-backed vault for memory.
The interesting thing isn’t the code. It’s that the abstractions an AI workstation needs are mostly the abstractions a Unix machine has had for forty years — shells, sessions, queues, cron, version control. The work was figuring out the right shape to wrap them in. Once that shape clicked, most of the implementation was straightforward.
If you find yourself drowning in agent tabs and lost sessions, build something like this. The design space is small. The payoff is enormous. And the boring tools really are the ones that hold up.