Guide

Replit vibe coding starts from a cold Repl. mk0r starts from a pool.

Same natural-language workflow. Very different first 30 seconds. Replit Agent spins up a Repl after you sign in. mk0r pings /api/vm/prewarm on page mount, a Firestore pool called vm_pool holds a sandbox that has already booted and initialized, and your first prompt claims it. No login, no cold start.

M
Matthew Diakonov
9 min
4.8from 10K+ creators
Firestore pool claims a ready sandbox
0 signup steps before first prompt
5 boot phases run before you type

The thing every Replit vibe coding tutorial leaves out

Every walkthrough on this topic covers the same loop. Sign in to Replit. Pick or create a Repl. Open the Agent panel. Describe the app. Watch files appear. The prose is correct but it hides the thing that actually defines the user experience: the boot wait between "I have an idea" and "the model is generating." That wait is the sum of account gating, Repl creation, dependency install, and dev-server start. You cannot prompt your way out of it; it has to be designed out.

mk0r designs it out with a pool. The landing page is coded to ping the pool on mount, the server tops the pool up to a target size, and every first prompt claims a ready sandbox. Replit's model assumes you are a Replit user, so the Repl is tied to your account and cannot be pre-created anonymously. mk0r is session-keyed by a UUID in localStorage, so the pool can be fully anonymous.

src/app/(landing)/page.tsx

That useEffect block is the entire client-side contract. Nothing in it blocks the UI. The fetch is fire-and-forget; it returns before the user has finished reading the hero. On the server, the ping invokes topupPool(), which looks at current pool size, subtracts ready and warming entries from the target, and kicks off prewarmSession() calls in parallel for the gap.

0Default VM_POOL_TARGET_SIZE
0 minPOOL_MAX_AGE_MS
0Boot phases done before claim
0Signup steps in front of you

The three constants that define the pool

The pool is a tiny amount of code doing a specific job. Three constants govern it. The collection name tells Firestore where to store entries. The max age tells cleanup when to kill a sandbox that has been warming too long. The target size tells topup how many ready entries to maintain.

src/core/e2b.ts

The 45-minute cap is the one worth pausing on. E2B sandboxes have a 1-hour timeout. If a sandbox sat in the pool for 59 minutes before claim, the claimer would inherit a sandbox about to expire. The 45-minute cap means stale pool entries are killed by cleanupStalePool() with 15 minutes of headroom so the claimer always gets real runtime.

What boots before your first keystroke

Pre-warming is not just "start a sandbox in the background." It is a five-phase pipeline that mirrors the hot path so that on claim, only the user-specific step needs to re-run. Here is the actual body of prewarmSession() with the comments the author left in place.

src/core/e2b.ts

What already exists when a pool entry is ready

  • E2B sandbox booted from pre-baked template (Node, Vite, Playwright)
  • ACP bridge initialized with a shared Anthropic API key
  • session/new called with /app as cwd and the baked system prompt
  • Model pinned to haiku for the streaming Quick mode
  • git init run inside /app so turn commits start immediately
  • Firestore doc flipped to status: 'ready' with a readyAt timestamp

How the claim actually happens

When your first prompt arrives, getOrCreateSession() tries three things in order. It checks for a live session tied to your session key. It tries Firestore for a paused session it can resume. Then, and this is the part most vibe coding tools do not have, it tries to claim a pre-warmed sandbox from the pool. Only if those three miss does it fall through to a cold boot.

src/core/e2b.ts

The void topupPool().catch(() => {}) on success is the bit that keeps the experience good at scale. The claimer does not wait for the refill. The next user still gets a warm sandbox because the pool refills in the background the moment the current claim returns.

Pool claim flow, end to end

Four actors, six messages. The landing page pings on mount. The API route tops up the pool. E2B boots a real sandbox. The ACP bridge inside the sandbox initializes. Firestore holds the status transition. When you finally press enter, the claim pulls from Firestore instead of booting.

From page mount to claimed sandbox

BrowserNext APIE2BFirestorePOST /api/vm/prewarmSandbox.create(template)sandbox, urlsPOST /initialize + /session/newpoolRef.set({ status: 'ready' })Submit first promptQuery vm_pool where status = readypool docPOST /initialize (user apiKey)session handed off

What the pool collapses into at runtime

The architecture is easier to see as a fan-in. Many inputs contribute to a single ready sandbox on the left. Many outputs fall out of that sandbox on the right. The hub in the middle is the pool entry.

vm_pool[status=ready] at claim time

Page mount ping
topupPool()
prewarmSession()
cleanupStalePool()
vm_pool doc
Claim on first prompt
ACP re-initialize
git history
Background refill

Watching a pool entry from the inside

The pool exists on disk as Firestore documents and as live E2B sandboxes. If you tail the logs during a prewarm, you see the state transitions in order. This is one warm-start as it appears in the [e2b] log stream on Cloud Run.

Cloud Run logs during a prewarm

What Replit does first, and what mk0r does instead

Both flows end with an AI agent writing code against a natural language prompt. They start at very different places.

Step-by-step: Replit Agent vs mk0r first prompt

1

You land on the page

Replit: the login card is the first thing. mk0r: the prompt box is the first thing, and /api/vm/prewarm has already fired.

2

You authenticate

Replit: enter credentials or sign in with Google. mk0r: no auth step. A UUID in localStorage becomes your session key.

3

You create or pick a workspace

Replit: choose a Repl template or start a new one. mk0r: no template picker. The pool already holds a Vite + React + Playwright template sandbox.

4

You describe the app

Replit Agent opens and accepts your prompt. mk0r routes the prompt into ACP against a sandbox that has already initialized.

5

The agent starts writing

Replit: the Agent begins, often after a dev server warm-up. mk0r: the dev server is already bound on port 5173 and the first edit hot-reloads in place.

A more honest side-by-side

Both are real products with real tradeoffs. Replit has things mk0r does not, including hosting, a multi-file editor, a marketplace, and full team features. mk0r trades breadth for a very narrow thing: the shortest possible path from a URL to a working AI-built app.

FeatureReplit Agentmk0r
Account required before first promptYesNo
Pre-warmed sandbox poolNot exposedYes, Firestore-backed vm_pool, target size tunable
Cold boot fallback timeVariable, depends on Repl type and accountAbout 2.5s (pre-baked template)
Agent protocolReplit-proprietary Agent channelAgent Client Protocol (ACP) over HTTP + SSE
Default modelReplit-routed, mostly AnthropicClaude Haiku for Quick mode, Sonnet/Opus on demand
Services pre-provisioned per sessionReplit DB and Repl Auth, everything else manualNeon, Resend, PostHog, GitHub (env vars in /app/.env)
First-turn git commitGit available, not pre-initialized per promptgit init runs inside the pool, ready on claim
Where the source for all this livesClosed sourcesrc/core/e2b.ts in the public repo, readable end-to-end

When Replit vibe coding is still the right answer

Two cases. First, when you want the full IDE surface: a proper file tree, multi-file editing, Replit's deploy pipeline, and long-lived hosted Repls with uptime. mk0r is a creation loop, not a hosting platform. Second, when you are teaching. The DeepLearning.AI and DataCamp courses walk through Replit Agent in detail and the screenshots line up with what you see on screen.

If what you want is the shortest possible path from idea to agent is writing code, the pool is the answer. It is not a Replit-shaped answer; it is a different answer.

What ships in the stack either way

The pre-warm is invisible to the user, but the moving parts are concrete and can be listed out.

E2B pre-baked templateFirestore vm_poolAgent Client ProtocolVite dev server on port 5173Playwright in-VM browserClaude Haiku default, Sonnet on demandNeon Postgres auto-provisionedResend API key scoped per appPostHog event streamGitHub repo per session

Try it as the Replit Agent user you already are

The easiest sanity check is the stopwatch. Open two tabs. Log in to Replit, create a new Repl, and start a new Agent chat with a simple prompt like "build me a habit tracker with email reminders." In the other tab, open mk0r.com and type the same prompt. Time from tab activation to first streamed token. That delta is almost entirely the pool.

The 0-minute pool lifetime, 0 default target size, and 0 boot phases that finish before you type are why the stopwatch comes out the way it does.

Want the pool explained against your own product?

Book a 20-minute walkthrough, show us what you are building, and we will open a live mk0r session next to a Replit Agent session so the boot difference is visible on screen.

Book a call

Frequently asked questions

What does 'Replit vibe coding' actually refer to?

Replit vibe coding is the workflow of using Replit Agent to turn a natural-language prompt into a running app inside a Repl. You log in to Replit, create or open a Repl, open the Agent panel, and describe what you want. The Agent writes files, installs dependencies, and starts a dev server. It is the Replit-branded take on the broader vibe coding concept Andrej Karpathy named in February 2025.

How is the first 30 seconds different on mk0r?

On mk0r there is no login step. You land on mk0r.com, the landing page fires POST /api/vm/prewarm on mount (src/app/(landing)/page.tsx:64), and the server tops up a Firestore-backed pool called vm_pool. By the time you finish typing your first prompt, a sandbox has already booted, called ACP initialize, called session/new, set its model, and run git init. Your first prompt claims that ready sandbox instead of waiting for one to boot.

Where is the pre-warm pool defined in the mk0r source?

In src/core/e2b.ts. POOL_COLLECTION = 'vm_pool' is declared at the top of the pool section. POOL_MAX_AGE_MS = 45 * 60 * 1000 (45 minutes) sets the staleness cutoff, which is lower than the 1-hour sandbox timeout so entries are killed before they expire. VM_POOL_TARGET_SIZE defaults to 1 and comes from an env var. prewarmSession() runs the full boot, initialize, session/new, set_model, and git-init pipeline and then persists status: 'ready'. claimPrewarmedSession() picks one up on the next request.

Does Replit also pre-warm Repls?

Repls are started per user, under an account, and paid-tier users get faster boots and always-on hosting. What Replit does not advertise is a global anonymous pool you can claim from before sign-in, because the product model assumes you are a Replit user with projects, storage, and a deployment target. mk0r's pool is session-keyed by a UUID in localStorage, so no account is tied to the ready sandbox.

What runs inside the pre-warmed sandbox before my first prompt?

Five things. First, the E2B template boots; the template is pre-baked with Node, Vite, Playwright, Xvfb, and x11vnc. Second, the ACP bridge is hit at /initialize with a shared Anthropic API key. Third, /session/new is called with /app as cwd and the default system prompt baked from src/core/vm-claude-md.ts. Fourth, set_model is called to pin haiku as the default. Fifth, git init runs so turn commits can start immediately. When you claim the session, only /acp/initialize is re-called with your credentials.

Which model does mk0r default to, and can I switch?

The pre-warm sets the active model to Claude Haiku for the streaming Quick mode. Heavier turns route through Claude Sonnet or Opus via ACP set_model. Replit Agent runs on its own routing, which currently favors Anthropic models but is not exposed as a per-turn switch in the same way.

What is pre-provisioned per session on mk0r that is not on a fresh Repl?

Per-session mk0r sessions get a Neon Postgres database (DATABASE_URL, NEON_HOST, NEON_DB_NAME), a Resend audience and scoped API key (RESEND_API_KEY, RESEND_AUDIENCE_ID), a PostHog app identifier (VITE_POSTHOG_KEY, VITE_POSTHOG_HOST, VITE_POSTHOG_APP_ID), and a GitHub repo at m13v/mk0r-app-<slug>. The provisioner lives at src/core/service-provisioning.ts. A fresh Repl is a filesystem and a runtime; any third-party service still requires you to sign up, paste a key, and wire it in.

What happens if the pool is empty when I hit mk0r?

The code falls through. claimPrewarmedSession returns null, the request logs pool_claim done with no claim, and the server calls createSandbox() to boot a fresh one. A fresh boot takes about 2.5 seconds for the template step. The pool is an optimization for the hot path, not a hard requirement. Either way there is no sign-in in front of it.

Is the pool size something I can tune in my own fork?

Yes. Set VM_POOL_TARGET_SIZE in the Cloud Run service env. The default is 1, which is enough for a single-user demo; raise it for load. getPoolTargetSize() reads the env at request time, so you can change it without a deploy by redeploying the env var. cleanupStalePool() runs against POOL_MAX_AGE_MS and kills sandboxes older than 45 minutes so the next prewarmSession starts clean.

Is the pre-warm a gimmick, or does it actually matter?

It matters most for the first perceived second. Vibe coding falls apart when the feedback loop is slow: type, wait for a Repl to spin up, wait for dependencies to install, wait for a dev server to bind, then start iterating. With the pool, the only wait on turn one is the LLM itself. Every retry, rollback, or follow-up on mk0r hits a sandbox that is already live, so the loop stays tight.

No sign-in. A sandbox is already warming for you while you read this.

Start a vibe coding session on mk0r