Start herePart 1 of 4

Anatomy of an AI app platform that ships to production

Turning a prompt into a running product is not a model problem — it's a systems problem. This is the map of the whole machine: how a chat message becomes an isolated Kubernetes workload with its own scale-to-zero Postgres branch, why the agent's work is gated by compilers and live probes instead of its own opinion, and how a release becomes a database branch promotion with a rollback anchor. Every other post on this blog zooms into one box of this diagram.

Anatomy of an AI app platform that ships to production

Ask a language model for a React app and you'll get one. That part works, and it has worked for a while. The distance between that and a product your users log into tomorrow — with their data intact after your next deploy — is not covered by a better model. It's covered by a platform: isolation, provisioning, verification, deployment, and recovery, each built so that the agent is allowed to be wrong and the system still converges.

This post is the map. Every other post on this blog zooms into one box.


The shape of the whole machine

A project on Adorable is born from a chat message and lives as real infrastructure:

reopened

promotion

passing

Kubernetes namespace (per project)

app pods
Vite + tRPC

catalog services
Redis · CMS · …

chat message

System architect
service selection · task plan

ReAct producer
writes code, runs tools

Objective verification
tsc · contract probes · in-pod tests

dev Postgres branch
copy-on-write, scale-to-zero

prod timeline
pinned commit + checkpoint

custom domain
real users

Five layers, five different failure domains:

  1. A generation engine that plans and writes the code — and is assumed fallible.
  2. A verification gate that decides what "done" means — and is not an LLM.
  3. A runtime that gives every project hard isolation — a Kubernetes namespace per project.
  4. A database layer built for thousands of mostly-idle, agent-created databases — copy-on-write branches that scale to zero.
  5. A deployment model where a release is a metadata operation with a mandatory rollback anchor — a branch promotion, not a copy.

The rest of this post walks the layers. Definitions for every coined term live in the glossary.


Layer 1: the generation engine plans before it types

A new project request never goes straight to code. A system architect stage reads the full service catalog, the project's current state, and the last generation's outcome, then commits to a plan: which services the app needs (database, API server, queue, CMS), how they integrate, and an ordered set of tasks with declared file scopes and acceptance criteria.

The producer is a single flexible tool-calling loop — it decides when to read context, write code, run SQL, and check its own work, with working integration examples for every active service injected into its context. Two disciplines keep a long build coherent:

  • Context compression with a progress tracker. Dropped history is summarized rather than lost, and completed actions (files written, SQL executed) survive trimming — so a 200-step build doesn't re-do step 12.
  • Anti-spiral enforcement. Diagnostic loops without code edits get broken by force; oversized single-file emissions are rejected before they hit disk.

The architecture treats the model as a replaceable part: planning, coding, and analysis run on separately configured models, and none of them is trusted with the next layer's job.

Layer 2: "done" is decided by evidence, not by the model

The producer cannot mark its own work complete. A claim of "done" runs through objective verification — layers that produce evidence the agent can't argue with:

  • the TypeScript compiler over every declared file, with import-shape and module-graph checks;
  • a contract probe: every API procedure the architect declared must respond on the live pod without server errors, and every declared table must exist — a procedure the agent forgot to write fails objectively, because the architect's declaration is the contract, not the producer's recollection;
  • the app's own unit tests, executed in its pod — every scaffold ships born testable, with a test runner and a working npm test from the first commit;
  • a headless browse of the running app when frontend files changed.

A failed layer reopens the task with the failure evidence in context. There is deliberately no "LLM critic" in this chain: a model grading a model converges on plausible, not on correct. Compilers, probes, and tests don't negotiate.

Layer 3: the runtime is a namespace per project

Every project runs in its own Kubernetes namespace — its own resource quotas, limit ranges, deployments, services, and ingress, generated from a declarative spec. The blast radius of any one app, including a misbehaving one, is its own namespace. Subdomain routing per app, wildcard TLS, and per-service web UIs all fall out of the same manifest generator.

A project isn't limited to one app, either: it can grow sibling apps and catalog services in the same namespace, and the platform converges to the declared set at one chokepoint — fail closed, never silently fall back.

Idle economics are a first-class design constraint. Apps nobody is viewing get frozen at the cgroup level (zero CPU, near-zero memory, instant thaw); their databases scale to zero compute. A fleet of weekend projects costs what it should: nothing while it sleeps.

Layer 4: the database layer was chosen for agents, not humans

The entity creating databases here is an agent that provisions hundreds of short-lived, mostly-idle databases — a workload that breaks the Postgres-per-app assumptions. The answer is separated storage/compute Postgres with copy-on-write branching (self-hosted Neon):

Cheap branching is also what gives the agent an undo button for data: it snapshots before risky migrations and rolls back when one breaks the data — code AND data together, to one coherent moment.

Layer 5: a release is a branch promotion with a rollback anchor

Going to production moves no data. Production is a durable timeline; dev is a copy-on-write fork of it. Each release pins a git commit, archives the released tree, tags a pre-deploy checkpoint on prod's timeline, and applies pending migrations — an append-only ledger row per release. Re-deploys keep production's data timeline; replacing prod data with dev's is a separate, explicit, snapshotted operation, never a side effect.

Rollback follows the same discipline: re-deploy the pinned commit, optionally restore data to that release's era — and the platform is explicit about what a rollback is allowed to physically delete: only what's recoverable from git or regenerable from the spec.


The connective tissue: built for interruption

Everything above assumes failure as a normal input. Builds die mid-flight (pod evictions exist); a reconciliation sweep detects every interrupted run and restores a consistent, resumable picture — job state, task chain, locks, chat flow. Secrets follow one mandatory path through Vault into namespace Secrets, so a container restart can never lose its credentials. Resource tracking records everything a project creates, so deletion is total.

None of this appears in a demo. All of it is the difference between a generated preview and software you can hand to a customer — which is the argument of the next post.


FAQ

What happens when the AI writes broken code? Verification catches it with evidence — a compile error, a failing contract probe, a red test — and reopens the task with that evidence in context. The agent fixes against facts, not against its own opinion of its work.

Where does my app actually run? In its own Kubernetes namespace on the platform: dedicated pods, quotas, ingress, and TLS, with its own PostgreSQL branch. Not in a shared sandbox.

What does an idle app cost? Effectively nothing. Containers freeze (zero CPU), database compute scales to zero replicas, and both wake transparently on the next request.

Is production data separate from dev? Yes — from the first deploy, production has its own data timeline. Deploys apply migrations to it; they never overwrite it with dev data. The destructive direction exists only as an explicit, snapshotted action.

Can I roll back a bad release? Every release pins a commit and tags a pre-deploy checkpoint. Rollback restores the code, and optionally the data, to that release's era.

Can I take my app elsewhere? A project is a standard git repository — Vite + React + tRPC + PostgreSQL, with migrations under db/migrations. It syncs to GitHub and runs anywhere that stack runs.

Build on the platform these posts describe.

Describe your app in plain English — Adorable writes the code, sets up the database, and ships it live.

Start building free