Building OmoiOS

How I built a self-hostable agent platform before background agents had a name, and what I learned shipping it without finding product-market fit.

Before the category had a name

In late 2024, AI coding agents existed but the category had no shared name. People called them “autonomous agents,” “AI engineers,” “AI coworkers,” “automation.” Cursor hadn’t shipped background agents. GitHub hadn’t shipped Copilot Workspace. Devin existed in demo form. The pattern of “describe what you want, an agent goes off and writes code, you wake up to a PR” wasn’t yet a product category buyers asked for. It was a behavior some of us were trying to make real.

I started building toward that behavior because I wanted to use it. The tools I had at the time required me to be the orchestrator: paste prompt, wait, read output, paste again, fix what broke, repeat. I wanted the orchestrator to be the software, not me.

That was the thing I was building. It would take six months to figure out it also had a name people would use once everyone else got there.

The itch

The frustration was specific. Agents were getting good at single tasks. They were terrible at coordinating across multiple tasks. There was no dependency awareness, no parallel execution, no structured handoff. If you tried to scale an agent across a feature instead of a function, you became the dependency graph yourself.

Worse: when something failed, you had no replay. Nothing was structured. Nothing was reproducible. Every run was bespoke.

I wanted three things:

  1. A way to take a high-level idea and turn it into a structured plan I could rerun and trust.
  2. A way to run multiple agents in parallel without them stepping on each other.
  3. A way for a supervisor to keep agents honest while they ran, instead of finding out at the end that they’d drifted into the weeds.

That was the brief I wrote for myself. It read short. The system underneath it did not stay short.

The shape it took

The system was simple in shape and stubborn in detail. I started a Claude Agent SDK run inside a Daytona sandbox, streamed updates from the sandbox to an API endpoint, and pushed those into an event queue the frontend subscribed to. That was the spine: sandbox to API to queue to UI.

The interesting part was what wrapped around that loop.

At the start of a run, the system snapshotted main and split off into a handful of branches I’d decided up front. Each branch carried its own completion criteria. Agents ran in a loop against those criteria, with a verification step every iteration. If verification passed, the branch was done. If not, the agent kept going, or got steered.

Underneath, that turned into more machinery than I initially budgeted for:

  • A spec state machine that walked a high-level feature idea through EXPLORE → PRD → REQUIREMENTS → DESIGN → TASKS before any code got written. Each phase had its own LLM evaluator acting as a quality gate.
  • A DAG executor so tasks with no shared dependencies could run in parallel, and tasks that depended on each other waited.
  • A per-agent guardian that analyzed each agent’s trajectory every sixty seconds and injected steering interventions when an agent started drifting from its goal.
  • A conductor on a five-minute interval, watching system-wide coherence and catching duplicate work across branches.
  • A convergence merge service that pulled the branches back together when their work landed, using Claude itself to resolve conflicts.

The result was a runtime where you described what you wanted at the top, gates pulled it into structure, agents executed in parallel across isolated branches, and the system kept watch on whether the agents were still doing the thing they were supposed to be doing.

What I underestimated

Two things, both about agents going off the rails.

The first was that agents drift. An agent given a coherent task will, over enough turns, find a way to do something the task did not ask for. Not maliciously. They get curious. They optimize an unrelated thing. They go deep on a side quest. Without something watching, the trajectory degrades.

This is what the Guardian existed to catch. Sixty-second intervals on every running agent. Each interval pulled the agent’s recent actions, scored them against the stated goal, and either let it continue or injected a steering intervention. The intervention was just text, but it shifted what the agent did on the next turn.

I underestimated how much of this you need. I thought one supervisor pass per task would be enough. It wasn’t. You need a Guardian per agent, a Conductor across agents, and a health check across the system. Three nested observation loops. Each one watching for something the others can’t see.

The second thing I underestimated was merging. The DAG let agents work in parallel cleanly. When their branches finished, the system had to put them back together. I built a convergence merge service that ran Claude as the conflict resolver. It worked. It also cost more than I expected, because conflicts were common and resolution required reading both sides of the diff plus the original spec.

The lesson: orchestration is not the hard part. Supervision and reconciliation are the hard parts. Anyone can spawn agents. Keeping them coherent and bringing the work back together is where the engineering lives.

What worked

A handful of patterns held up under load.

The spec phases were correct. Walking a fuzzy idea through EXPLORE → PRD → REQUIREMENTS → DESIGN → TASKS before any code ran was the single highest-leverage decision in the system. Each phase was a quality gate. Skipping the gates produced bad work fast. Walking them produced slow, careful, defensible work that agents could execute against without inventing their own context.

Sandboxes per agent were correct. Every agent got its own Daytona container, its own filesystem, its own Git branch. No shared state. No interference. When something went wrong, the blast radius was one sandbox. Cleanup was killing one container.

Dependency-aware parallelism was correct. Tasks ran when their dependencies were satisfied, not when their position in a queue came up. This is the difference between five agents working at once and five agents waiting for each other in a queue.

Letting Claude resolve merges was unexpectedly correct. I’d assumed conflict resolution would be the worst part of the system. With a structured spec and isolated branches, conflicts were usually small, and an LLM that could read both sides of a diff plus the original requirement could resolve most of them without escalation.

The project hit 60 stars on GitHub, ran for real users, and produced PRs against real repositories. The demos were not staged. The system actually worked.

Why I stopped pursuing it commercially

Customers weren’t coming. The product worked, the demos worked, and the trickle of paying users I’d hoped for stayed a trickle. Meanwhile the landscape shifted underneath me. Open-source alternatives showed up: agentos, the various oh-my-* meta-frameworks, a long tail of “your own background agent runtime” projects. Cursor shipped background agents inside the IDE. GitHub shipped Workspace. Buyers who would have considered OmoiOS as a managed service now had it bundled into tools they already paid for.

I could have kept pushing. Better positioning, better marketing, a sharper wedge. I didn’t. The honest read was that the shape I’d built was a platform, and the market had decided it wanted features inside the tools it already used, not a platform sitting alongside them.

So I open-sourced what I’d built, released it under Apache 2.0, and started thinking about what to do with the patterns underneath it. Two paths looked viable. One was an SDK that lets LLMs interact with other systems cleanly. The other was building the same multi-tenant agent infrastructure I’d built for OmoiOS, but as a service for companies who were now trying to put agents into their own products.

The way I’d been going about it before wasn’t working. Something needed to change.

What I’m bringing forward

The multi-tenant work I do now is OmoiOS turned inside out.

OmoiOS tried to be the agent product. Customers would sign up, hit my managed service, and get PRs back. The market said no. The companies who actually had the buyer intent for this were building their own agent products and running into the same wall I had: how do you take a single-tenant agent demo and make it work for many customers without inventing tenant isolation, GitHub App flows, sandbox orchestration, and supervisor patterns from scratch?

Every one of those problems I’d already solved for OmoiOS. The Daytona sandboxes per agent. The Git branch isolation. The supervisor that watches for drift. The merge service that pulls work back together. None of it was specific to a managed service. All of it was infrastructure for “many agents, many tenants, working in parallel without stepping on each other.”

That’s what I sell now. Not the product. The layer underneath it.

OmoiOS itself stays open source. The patterns it taught me are the spine of the work I do for clients. The next person who builds an OmoiOS-shaped product won’t need to spend a year figuring out the things I spent a year figuring out. They can fork the code, hire me to put the multi-tenant boundary in, and ship faster than I did.