Guide

Instant AI App Builder: How mk0r Has a Sandbox Ready Before You Type

"Instant" is the part of the pitch most AI app builders cannot back up. We can, and not because we are faster at booting VMs. We are faster because we removed the one thing that forces every other builder to boot serially: the signup gate.

M
Matthew Diakonov
7 min

The thesis: removing signup is what makes the parallelism possible

When you load mk0r.com, three things start at the same instant: a Firebase anonymous sign-in spins up in the background, a fetch to /api/vm/prewarm fires fire-and-forget, and the input box renders. By the time you have read the example prompts and decided what to build, the server has booted a real cloud sandbox, initialized an agent session inside it, set the model to Haiku, and provisioned a git repo. The sandbox is sitting in a Firestore collection named vm_pool, waiting for your browser to claim it atomically.

None of that can run in parallel if you put a signup wall in front. A builder that asks for an email first has to wait until you have created an account before it can spend money booting a sandbox. By the time you finish entering a password and confirming an email, the cold-boot clock has not even started. Anything that promises "instant" on a signup-gated flow is using a different definition of instant than the one in the dictionary.

The rest of this page is the latency math, the file paths, and an honest section on the tradeoffs.

What happens at t = 0

Open the browser DevTools network panel and load mk0r.com. Inside the first frame after React mounts, you can watch three independent timelines start.

Concurrent timelines at page mount

BrowserFirebasemk0r APIE2B / Firestoreanonymous sign-inPOST /api/vm/prewarm (no auth)topupPool() reads vm_poolready countprewarmSession() if deficitanonymous uidready row writtenuser types and clicks sendclaimPrewarmedSession (txn delete)live sandbox URLs

The first three messages start in the same animation frame in a real browser. They are not awaiting each other. The Firebase anonymous sign-in and the prewarm POST are started by separate useEffect hooks, both with empty dependency arrays, and they do not block paint.

The exact lines that make this work

This is not a marketing claim, it is six lines of code. The prewarm fire lives inside the landing page component:

// src/app/(landing)/page.tsx
useEffect(() => {
  fetch("/api/vm/prewarm", { method: "POST" }).catch(() => {});
}, []);

That is the entire mount-time hook. There is no await on the result. There is no auth header. The dependency array is empty, so it runs once on mount, before any other effect that touches the session key, before the auth provider has resolved anonymous sign-in, before the user has scrolled.

The endpoint that receives it is equally permissive. In src/app/api/vm/prewarm/route.ts, the auth check is opt-in:

function checkAuth(req: NextRequest): boolean {
  const required = process.env.POOL_ADMIN_TOKEN;
  if (!required) return true;
  const header = req.headers.get("authorization") || "";
  const token = header.startsWith("Bearer ") ? header.slice(7) : header;
  return token === required;
}

On the public site, POOL_ADMIN_TOKEN is unset, the function returns true, and any browser can nudge the pool. The only side effect is that the server tops up to its target size if it is below it, which costs one (idle) E2B sandbox at most. The full topup logic, including pool inspection, stale cleanup, and fire-and-forget spawning, lives in topupPool() in src/core/e2b.ts.

What is in the pool

When the server warms a sandbox, it does not just reserve a slot. It walks through five real bootstrap steps inside prewarmSession() and only writes a Firestore document when all of them succeed:

  1. Boot a fresh E2B sandbox from the mk0r-app-builder template.
  2. Send POST /initialize to the in-VM agent client protocol bridge, with the shared API key. This is the slowest call in a cold start.
  3. Send POST /session/new with cwd: "/app", the MCP servers config, and the default app builder system prompt. This is the second slowest call.
  4. Set the active model to Haiku via POST /session/set_model.
  5. Provision a git repo for the session and capture its repoId, identityId, and gitToken.

All of that is paid for and complete by the time the document is marked status: "ready". When you press send, claimPrewarmedSession runs a Firestore runTransaction that filters status == "ready" and specHash == currentSpecHash, reads the first match, deletes it, and hands the live sandbox to your session key. A second visitor cannot claim the same row because the transaction is atomic.

The only remaining cost on your side is one ACP /initialize with your real credentials (which restarts the agent subprocess inside the VM if needed) and a quick echo ok liveness probe. The cold-boot work, the hard part, is already done.

Why a signup gate would break this

Picture the same diagram with an email/password screen between page mount and prewarm. Now the server cannot start a VM until it has a verified user, because every cold sandbox is a real cloud cost and you cannot let strangers DOS your bill. The timeline becomes serial.

FeatureSignup-gated buildersmk0r
When the VM starts bootingAfter signup completesWhen the homepage mounts in your browser
Auth in the boot pathEmail + password (or OAuth) before any sandbox costAnonymous Firebase sign-in in parallel with prewarm
Steps that run seriallyForm, verify, create user, then 5-step VM bootJust the 5-step VM boot, mostly already done
What you wait for after pressing sendCold boot of the agent runtime (~5-10s)One Firestore transaction + ACP re-initialize (~2s)
Cost of an idle visitorZero (no VM until they sign up)One warm sandbox at most, capped by pool size

The point is not that signup-gated builders are wrong. They make a different tradeoff: lower idle cost, slower first build. We picked the other side. We trade a small steady-state cost (one idle E2B sandbox at any given time, by default) for a build experience that starts the second you finish typing.

The boundaries of this argument

Three things this page is not claiming.

First, the pool is small on purpose. The default VM_POOL_TARGET_SIZE is 1. If two strangers land on the homepage in the same second and both press send before the topup re-fires, the second one falls back to a fresh boot. The fast path is the common path, not a guarantee. For most traffic shapes a target of 1 is the right tradeoff because 95% of arrivals never press send.

Second, the pool only helps the very first build. After that, your sandbox is yours. Subsequent prompts re-use the same VM via session reuse, not the pool. The pool is only the on-ramp.

Third, generation itself still takes time. Once the agent inside the VM starts working, it is bounded by Haiku's streaming throughput and whatever tools the agent invokes. "Instant" means the runway from press-send to first-token, not from press-send to done-building. We measure roughly 30 seconds for a Quick mode HTML app and 1 to 3 minutes for a VM mode React project, which matches what the homepage stat band on mk0r.com says.

The honest version of the claim: removing the signup gate buys you about five seconds of latency that would otherwise be unavoidable. Five seconds is not magic, but it is the difference between a builder that feels alive and one that feels like a queue.

Want to watch the prewarm fire in real time?

Book a 20 minute call. We will open mk0r.com with the network panel running, watch the POST /api/vm/prewarm go out at t=0, and tail the server-side e2b.pool.warming_start log as a fresh sandbox enters the vm_pool collection. You can press send while we watch claimPrewarmedSession run the Firestore transaction live.

Frequently asked questions

Why is 'no signup' connected to being instant? They sound like separate features.

They share a cause. A sandbox costs real money to keep running, so a builder that gates behind signup cannot pre-warm sandboxes for anonymous visitors. It has to wait until it knows who you are, then start a sandbox, then run you through onboarding. Each of those is a serial step. mk0r removes the signup gate, which lets the homepage fire POST /api/vm/prewarm on mount with no auth header. The server tops up a Firestore-backed pool of pre-booted E2B sandboxes while you read the hero copy. By the time you press send, the round trip is one Firestore transaction and one ACP re-initialize, not a five second cold boot.

Where exactly is the prewarm fired in the code?

src/app/(landing)/page.tsx, inside the Home component, in a useEffect hook with an empty dependency array. The body is fetch('/api/vm/prewarm', { method: 'POST' }).catch(() => {}). It runs on the first render of the landing page. There is no auth check ahead of it and no await, so the React tree continues mounting while the request is in flight. The prewarm route itself, src/app/api/vm/prewarm/route.ts, allows unauthenticated POSTs when the POOL_ADMIN_TOKEN env var is unset, which is the default for the public site.

What is the pool actually storing? Is it a real VM or just a token?

It is a real running E2B sandbox. src/core/e2b.ts defines POOL_COLLECTION = 'vm_pool' and stores one Firestore document per ready sandbox. Each document has the sandboxId, host, acpUrl, previewUrl, sessionId, modes, models, agentCapabilities, repoId, identityId, gitToken, historyStack, and activeIndex. The sandbox itself is alive in E2B with the agent client protocol process initialized, an ACP session opened with the app builder system prompt, the model set to haiku, and a git repo provisioned. Five steps that already happened.

How big is the pool?

By default, one. The size is read from process.env.VM_POOL_TARGET_SIZE at src/core/e2b.ts line 1899, with a fallback of '1'. If you press send and the pool has a ready entry that matches the current spec hash, claimPrewarmedSession runs a Firestore transaction that pops it. If the pool is empty, you fall back to a fresh boot. After your claim, topupPool fires a fire-and-forget prewarmSession() so the next visitor finds a ready row again.

What stops the pool from holding stale, dead sandboxes?

Two guards. POOL_MAX_AGE_MS at e2b.ts:1896 is set to 45 minutes, just under the 1 hour E2B sandbox timeout. cleanupStalePool walks every doc, kills the underlying sandbox if it is older than that or its specHash no longer matches, and deletes the document. The specHash is computed from the system prompt, MCP server config, and other agent settings, so the pool turns over automatically when those change. claimPrewarmedSession also runs a deep liveness probe (execInVm 'echo ok') after claiming and discards the sandbox if the probe fails.

How fast is the claim path versus a cold boot?

A cold boot has to do all of: create sandbox, ACP initialize, ACP session/new, set_model, git init. The first three calls each carry a network round trip into E2B and are gated by AbortSignal.timeout values of 30 seconds, 30 seconds, and 30 seconds in prewarmSession. In practice cold is on the order of 5 to 10 seconds. The pool path skips all of that. If your real credentials match the shared key the pool was initialized with, only the liveness probe runs and the sandbox is yours. If they do not, ACP restarts the subprocess and one fresh session/new call runs, which is a couple of seconds.

Doesn't keeping a pre-warmed sandbox cost money for traffic that never converts?

Yes, that is the tradeoff. A pool target of 1 means at any time at most one paid E2B sandbox is sitting idle waiting for a visitor. POOL_MAX_AGE_MS caps how long an idle sandbox can live before cleanup. The math only works because the cost is small (one warm sandbox, not a fleet) and because the conversion-from-instant-experience is high enough to justify it. A tool that gates behind signup picks the opposite tradeoff: zero idle cost, but multi-second cold start for every first-time builder.

What about my second visit? Is my own sandbox there too?

Yes, but through a different path. On the first visit your sessionKey lands in localStorage as mk0r_session_key. The active session, including its sandboxId, is persisted in Firestore under that key. When you come back, getOrCreateSession reuses the existing sandbox if it is still alive, or revives it. Pool prewarming is for the very first visit when there is no session key yet. The two systems are separate.

Can I see this for myself?

Yes. Open mk0r.com, then open the browser DevTools network tab before the page finishes loading. You will see a POST request to /api/vm/prewarm fire with no authorization header. The repo is open source at github.com/m13v/appmaker if you want to read the implementation: src/app/(landing)/page.tsx for the mount-time fire, src/app/api/vm/prewarm/route.ts for the endpoint, and src/core/e2b.ts for prewarmSession, claimPrewarmedSession, and topupPool.

Open the homepage. The sandbox is already warming.

Build Your App
mk0r.AI app builder
© 2026 mk0r. All rights reserved.