The compiler is a schema
You type “a button that counts”, and a few seconds later there is a URL, and at that URL a real Next.js app is running the thing you described. The obvious mental model for how that works is an agent: a model that plans, writes files, runs a build, reads the errors, tries again. CodeCapsule has none of that. No agent loop, no tool calls, no planner, no file tree, no retry. The part of it that does the work is a forty-line zod schema, and I think it is one of the cleaner demonstrations I have built of an idea worth stealing: a type can be a contract that runs.
One object, four rooms
Here is the whole shape of the system before any detail. A schema defines one object. The model is asked to fill that object in. The same object, with no translation, is streamed to the browser, rendered into the interface, and then read field by field as a work order for a virtual machine. It passes through four completely different runtimes, the model, the network stream, the React app, and a sandbox, and at no boundary does anyone write an adapter, a DTO, or a mapping function. The object is the interface between all of them.
The capsule schema is the only contract. It crosses four runtimes without a translation layer at any boundary.
That is the thesis. Everything below is what the schema actually says, and, more interestingly, what it cannot say.
The schema is the prompt
The system prompt is seven lines. It tells the model it is a skilled engineer, that it does not make mistakes, to generate a code capsule, and lists the templates it may use. That is the entire natural-language instruction. Every real constraint lives in the schema’s field descriptions: “Max 3 words” on the title, “Only runnable code is allowed” on the code, “Do not include dependencies already included in the template”. Because the AI SDK compiles a zod schema’s descriptions into the model call, the type definition and the prompt are the same artifact. They cannot drift, because they are one file.
Two decisions inside that object are worth slowing down for, because they are invisible if you read the schema as a mere validator.
The first is field order. When a model emits a structured object, it emits it
as JSON, in the order the keys are declared, and commentary is deliberately
the first key. So before the grammar of the JSON will let the model write a
single character of code, it has already had to write a paragraph describing
what it is about to do. The schema is not just validating the output, it is
forcing the model to think before it acts, purely through the order of two
keys. Chain of thought, implemented as property ordering.
The second is what the object streams into. The browser does not wait for a
finished response and parse it. It re-parses the growing, half-finished JSON on
every chunk, so commentary types itself into the chat bubble while the model
is still writing it, and then code pours into the editor pane a moment later.
One stream, one object, two live surfaces, and no protocol beyond “the keys
arrive in order”. Step through it:
The keys arrive in schema order. Step through the stream and watch each field route itself to a different surface.
One streamed object, routed field by field. commentary and the titles feed the chat; code feeds the editor; the rest becomes the sandbox’s work order.
Notice what the schema quietly cannot express. code is a single string and
file_path is a single string. One file, by construction. There is no way for
this object to describe an app with two files in it, and so there are no
two-file apps. The limits of the type are the limits of the product, exactly.
Eight providers, one branch
The same object has to be fillable by any model. CodeCapsule lists nineteen of them across eight providers, and the machinery that makes them interchangeable is almost nothing, because the schema absorbs the differences. Five providers have real SDK integrations. The other three, Groq, Together and Fireworks, are the OpenAI client pointed at a different hostname, because the OpenAI wire format has become the de facto standard the rest of the market implements. Three of the eight “providers” are one client with the base URL swapped.
The only per-model special case in the entire codebase is this:
export function getDefaultMode(model) {
if (providerId === "fireworks") return "json";
return "auto";
}
For most models the SDK picks the best way to coerce structured output, usually the provider’s tool-calling machinery. Fireworks-hosted Llama gets forced into plain JSON mode instead. That one branch is the whole cost of supporting nineteen models against a strict schema. Everything else the schema handled.
Execution is a file write, because the machine was already running
The last field of the object becomes a URL, and this is where the design is most quietly clever. When you “run” the generated code, nothing is built, nothing is installed, and no server is started. All of that was paid for once, in advance, when the sandbox template was built.
The template’s Docker image runs create-next-app, then installs every single
shadcn component, so that any component the model imports resolves without a
network call. Then, still at build time, it starts the dev server and polls the
homepage with curl every tenth of a second until it answers with a 200. Only
then does the platform snapshot the machine, and the snapshot captures it live:
dev server running, homepage already compiled.
All the slow work happens once, at template build time. What the user waits for is a resume, a file write, and a hot reload.
So at request time, creating the sandbox resumes that snapshot, and the machine
comes back with its dev server already listening. “Executing” the model’s code
is one line: write the generated file to pages/index.tsx. Next.js hot module
reloading sees the file change and swaps the page in. The overwrite is the
deployment. The template’s promise, “reloads automatically”, and the snapshot
taken mid-run are two halves of the same trick, and together they are why the
gap between typing a sentence and seeing it run is seconds instead of minutes.
Scar tissue
Now the honest part, and with this system the honesty is load-bearing, because the schema’s elegance and the product’s thinness are the same fact seen from two sides.
The route that runs the code is wide open. It accepts arbitrary JSON over an unauthenticated endpoint and runs a model-authored shell string on the operator’s sandbox account, with the rate limit sitting on the generation route instead, and even that rate limit is skipped whenever the request carries any non-empty API key string, which the caller supplies. The safety of running generated code rests entirely on the sandbox provider’s virtual-machine isolation and a ten-minute timeout, not on anything in this code. That is a real posture, defensible for a demo, but it is the provider’s guarantee, not mine, and the difference matters.
The Python path is a facade. There is a branch for a code-interpreter template that creates the interpreter, installs dependencies, and then never runs the code, returning a web-app result shape regardless. The renderer for interpreter output, cell results, standard error, image outputs, is complete and entirely unreachable. It is the fossil of a second product that was scaffolded and never wired. Auth is commented out. A shipped typo, a slash where a dot belongs in one base URL, silently breaks both Groq models unless the user overrides the host by hand, which is proof the full model matrix was never exercised. And “You do not make mistakes” in the prompt is not a joke, it is the actual error strategy, because sandbox output never returns to the model and there is no repair loop. If the generated code crashes, you get a broken frame.
None of this is hidden in the code; it reads exactly as what it is, a sharp demonstration of one idea with the unglamorous 80 percent left as a to-do list. Which is the honest shape of most things that are fun to build.
What the schema was really doing
The lesson I keep is about where to put the hard thinking. CodeCapsule has no architecture to speak of, two API routes and a page, and yet it does something that feels like it should need an agent, because it moved all of its structure into a single typed object and then let four different runtimes agree to speak in terms of that object and nothing else. The prompt, the streaming protocol, the UI’s two panes, and the machine’s work order are not four contracts kept in sync. They are one contract, read four times.
Design the object, and the system falls out of it. What the object can’t say, you don’t ship.