Not a loop, on purpose
Here's an irony we live with happily: about 95% of the pull requests that build this platform are written by an agent loop — a model calling tools, reading files, iterating until the work is done. And the product itself, the thing that turns your sentence into a running app, deliberately refuses to be one.
When people hear "AI app generator" in 2026, they picture a small server-side Claude Code: the model gets a read_file tool, pokes around the project, decides what to look at, edits, checks its work, goes again. We recently sat down and characterized what moving to that architecture would actually cost us, layer by layer. The conclusion was clarifying: our generator being not a loop isn't technical debt waiting to be modernized. It's the design, and it's load-bearing.
The generator is a pure function
Every generation turn on Vibes DIY is one deterministic assembly: system prompt, reconstructed conversation history, context slots, your new message — built in a single pass from database rows, sent as one request, streamed back as one response. Same state in, same request out. No hidden rounds, no model-chosen detours.
That purity pays rent in four places:
- One stream per turn. The whole streaming stack parses a single model response into typed sections and persists it as it flows — the code you watch appear in the editor is the same stream being written to the database.
- Recovery. When a stream truncates mid-generation (networks are networks), we can stitch it back together, because a turn is one stream with a known prefix. Keep the safe cut, resume, done.
- Cost control. Context-stuffing is managed by compaction and slot dedup that operate on a static message list. You can reason about exactly what the model will see, and exactly what it costs, before sending anything.
- Replay and evals. The same assembly runs with zero side effects, so we can dry-run any turn, replay history, and score one-shot generations by the batch. A prompt change is measurable within a coffee break.
Get posts like this in your inbox
One email field. Real updates. No algorithm required.
There's a subtler property hiding under those four: visibility is guaranteed by construction. If the model couldn't see a file it needed, that's a server bug — findable with a deterministic test, fixable with a deterministic change. We know because we recently shipped exactly that fix: remixed multi-file apps were showing the model only one file, and the model — quite rationally — announced it would "rewrite the app standalone." The fix was seed parity and a unit test. In a tool-loop world, the same symptom becomes "the model didn't choose to read the file": a behavioral problem you can only manage with eval batteries and prompt nudges, never actually close.
What the loop would cost
We walked the pipeline and priced the conversion honestly. It's not an incremental change to prompt assembly — it converts a pure function into a stateful agent runtime, and nearly every hard-won mechanism has to be rebuilt in a new shape:
- The turn becomes N round-trips. One request becomes model → tool call → execute → result → model, several times, inside a Durable Object with wall-clock and subrequest limits. The server now owns loop state: which reads happened, what's been fed back, when to stop.
- Persistence has no vocabulary for it. Our turn schema stores prompts and code blocks; tool rounds mean extending it and teaching history reconstruction to replay (or elide) them on every later turn. Old tool results become the new context bloat — you re-invent compaction for accumulated reads.
- Recovery gets qualitatively harder. A truncation can land mid-loop. Stitching needs per-round checkpoints and idempotent re-entry instead of "keep the prefix of one stream."
- Gravity pulls writes in too. A read-only tool bolted onto fenced SEARCH/REPLACE output is an unusual hybrid; models trained for tool loops want
edit_fileas well. Once writes move, the entire fence pipeline — streaming resolver, apply-error observers, carry-forward semantics — gets replaced rather than reused. - The multi-model story shrinks. One-shot fenced output is the lowest common denominator that works across every provider we route to. Streaming tool arguments, parallel calls, and schema quirks differ per provider — and our own agentic evals show the tool loop is exactly where smaller and open-weight models get shaky. The pure function keeps the model market open; the loop would put that openness at risk.
Call it what it is: replacing the engine, not tuning it — building a small server-side coding agent, with the risk concentrated in recovery and cross-model tool reliability.
When we'd reach for more
This isn't dogma; it's sequencing. The loop's payoff only materializes when apps outgrow context-stuffing — many large files where slots, dedup, and compaction stop being enough. And there's a bounded middle step before any loop: a single "pick which files you need" pre-pass, followed by the exact same one-shot turn. That captures most of the token savings for one extra round and zero new persistence, recovery, or protocol machinery. If the evals ever tell us the full loop is worth it, we'll know — because measuring one-shot generations cheaply is one of the things not being a loop buys.
The deeper point is about fit. An agent loop is the right architecture for open-ended engineering — it's how this platform gets built, every day. But a product turn that has to be fast, recoverable, replayable, and cheap the millionth time it runs wants to be a pure function. We use the loop to build the not-loop. Each is a strength precisely where the other isn't.
One sentence in, one app out.
No agent wandering your files — a single pass from your idea to running code, live in seconds.
Start building →