01 / 05·build-in-public·6 min read

I built the engine and the product at the same time — on purpose

fabri the engine ludexel the game it ships a failure → becomes a guideline ← cheaper default makes it cheaper to run
The whole series in one diagram: the product breaks the engine, the engine gets cheaper, repeat.

On June 21, one day after my agent framework’s first tagged release, a crash report landed that named its own source: “Reported by the ludexel service, which couldn’t import fabri.” The cause was a small but real Python bug — a method name inside fabri accidentally collided with a type hint elsewhere in the same file, and only showed up once something outside the framework actually tried to import it. It wasn’t a bug ticket filed by a stranger. It was my own game studio, one day after go-live, breaking the framework I’d just shipped, because it was actually trying to use it.

I’m building two things at once, and that crash report is what happens when they’re genuinely connected. ludexel is an AI-native game studio whose promise is “prompt in, playable Game Boy Advance ROM out.” fabri is the agent engine ludexel runs on — the part that plans, calls tools, and remembers what worked last time. I didn’t plan the parallel build on day one. fabri started two days earlier as a plain memory layer for agents, not an engine; it earned the title “engine” later, once a real product started leaning its full weight on it. What I did do on purpose was keep both projects running side by side once that dependency existed, because a framework with no demanding user just accumulates opinions nobody ever tests.

The rename had zero users to break

v0.1a memory layerv0.4in productionv0.7ten gaps closedv0.8a daily toolv0.10now measured Jun 19 Jul 7
v0.1 → v0.10 in three weeks. It shipped as a plain memory layer and became "the engine" only once a real product started leaning its weight on it.

fabri’s own roadmap is refreshingly blunt about its own early history: an early rename “landed before any external consumer existed, so there was nothing to deprecate.” That grace period lasted about thirteen hours. By the next morning, a real service was importing fabri in production, and from that point on, every change to fabri had to survive a pipeline that actually shipped playable ROMs — not a hypothetical one.

That pressure shows up everywhere in how ludexel actually runs fabri day to day: one orchestrator, several specialist sub-agents for story, characters, maps, items, and battles, each spun up from the same core loop with only its instructions swapped out. Every safety limit in that setup — how many steps an agent gets, how much a run is allowed to cost before it’s cut off — exists because a real run once blew past a looser limit and nobody noticed until the bill or the log did. None of those limits were designed up front on a whiteboard. They’re what a real product does to an engine that’s actually running on it, not merely being demoed with it.

Shipping the gaps between the pitch and the code

The clearest example of that forcing function is a release cycle where I sat down and checked fabri’s own pitch — “a self-improving agent runtime with honest cost tracking” — against what the code actually did, and found ten places where the claim was ahead of the implementation. All ten shipped together, with tests, in one push: a running view of cost over time instead of a single lump number, a breakdown of which tool calls actually burn the budget, and a measure of how often the memory system reuses a past lesson instead of quietly re-learning it. You can watch fabri’s changelog fill in over time exactly this way — not as one big redesign, but as a long run of small, honestly-titled fixes, most of them written the same day something in ludexel exposed the gap.

The release right after that one carried an equally blunt note in its own description: this batch touched nothing that ludexel depended on. Writing that down mattered, because most releases do touch it — a release that changed nothing downstream was the unusual case, not the default.

The loop runs both directions

The obvious direction is ludexel’s failures becoming fabri’s guidelines. One fix in ludexel came from an agent inventing a character sprite that didn’t exist in its own asset list, and separately from the orchestrator announcing “the game is ready to play” off a check that had never actually run the build. Both fixes came from the same instinct: stop the system from asserting things it hasn’t actually verified. A later retrospective on the studio’s flagship demo — a five-scene showcase that replaced a twenty-scene draft that simply didn’t work — got folded straight back into the prompts the specialist agents use, so the lesson binds future runs instead of living only in one person’s memory.

The less obvious direction is fabri getting cheaper making ludexel cheaper to run. fabri switched its default model provider away from the more expensive option, not chiefly for quality but because ludexel’s own fan-out — several specialist agents cooperating on one build — made cost add up fast enough that a cheaper, higher-rate-limit default mattered more than a marginal quality edge. A lightweight “here’s what I’m doing” narrator role runs on the cheapest model available for the same reason: you don’t need your most capable model to say “working on step three.” The same product that broke the framework on day two is now shaping which model runs by default.

fabri’s own README is upfront that this cost-consciousness is inherited, not invented — it cites the broader industry’s well-known warning that unmanaged multi-agent setups can run many times more expensive than a single agent doing the same job, and that’s a real part of why fabri defaults to a frugal, single-threaded posture and gates any fan-out behind an explicit prompt. It’s honest, too, that fabri hasn’t yet measured its own single-agent-versus-fanned-out cost delta directly — the industry number is a reasonable prior, not a measurement fabri has run itself.

Where I’m honest about the gap

The loop isn’t automatic. I have to personally notice a ludexel failure and decide to route it into fabri, or notice fabri getting cheaper and go update ludexel’s config — nothing wires that connection for me. The safety checks drift, too: ludexel’s automated checks catch a game world that’s technically broken — disconnected rooms, missing exits — but for weeks they didn’t catch one that was technically valid and simply empty: a lifeless scene with nothing in it. That gap got closed by hand, with a new check requiring every reachable scene to have either a character in it or a non-trivial layout. The prompt that tells the orchestrator about its own safety net still hasn’t caught up — it still tells the agent, incorrectly, that this exact failure mode won’t be caught automatically. The fix landed before the documentation describing it did.

Measurement doesn’t equal quality, and I learned that lesson twice — once in the game content, once in fabri’s own memory-retrieval code. An evaluation gate I’d built to score how well fabri’s memory system retrieves the right past lesson caught, on its very first real run, a case where the “hybrid” retrieval mode — supposedly combining keyword search with meaning-based search — was silently behaving exactly like meaning-based search alone. The keyword half was being built in a way that made it require every single word in a query to match, so on any multi-word query it almost never contributed anything. A small fix later, the measured score jumped meaningfully, and for the first time hybrid search was actually doing something different from plain search, not just claiming to.

Both projects are still one person’s judgment call away from drifting apart, and I don’t think that risk ever fully goes away. I just think it gets cheaper to catch the moment a product is real enough to actually break the engine underneath it — and fabri’s own documentation and release history are where that ongoing back-and-forth actually gets written down, one honestly-titled change at a time.

— reads