Guide

Vibe coding retention limits are four clocks, not one

Most guides on this topic answer a different question. They tell you when your weekly token cap resets, which is a rate-limit story. Retention is what your session actually keeps, where it lives, and what kills it. In mk0r there are four separate clocks. Here are the constants and the file paths so you can verify them yourself.

M
Matthew Diakonov
9 min

Direct answer · verified 2026-05-01

How long does a vibe coding session retain state?

In mk0r, four independent layers, four lifetimes. The live sandbox: 1 hour idle, refreshed on every prompt. The pre-warm pool slot: capped at 45 minutes before a sweeper kills it. The conversation transcript: indefinite, reloaded from a Firestore document via /session/load after the sandbox dies. The per-turn git history: unbounded, every prompt commits an SHA to historyStack. Every constant cited below is named to its file path so you can grep for it directly.

0minSandbox idle window
0minPool slot max age
0Independent retention layers
0Firestore doc per session

Why this is four questions, not one

When someone types this topic into a search bar they usually mean one of two things. Either they want to know whether their work is safe (will my project still be there tomorrow?) or whether they will run out of runway mid-build (does the AI keep talking to me, or does it forget partway?). Those are different stacks of state on different clocks, and the answer depends on which one you are asking about.

A vibe coding session in mk0r has at least four retainable things, and each one has a different lifetime, a different eviction trigger, and a different recovery path:

  1. The live sandbox VM. The Linux container running your Vite dev server, Chromium, and the ACP agent. This has the strictest clock and the most visible effects when it dies.
  2. The pre-warm pool slot. An idle, fully-booted sandbox sitting in a Firestore-tracked pool, waiting for the next visitor to land on the homepage. This clock exists only because the sandbox clock exists.
  3. The conversation transcript. The list of prompts and AI responses that the agent uses for context on the next turn. Held by the ACP agent inside the VM, but reconstructible from a Firestore document.
  4. The per-turn git history. An ordered stack of commit SHAs, one per prompt, that lets the user undo back to any earlier version of the file tree.

The interesting thing is that every single one of those clocks has a different value, and the source for each value is verifiable in a single file. Below are the constants.

Four retention layers, four eviction triggers

1

Sandbox

1 hour idle, refreshed on every prompt

2

Pool slot

45 min cap, swept on traffic

3

Transcript

Indefinite, Firestore-backed

4

Git history

Unbounded, per-turn SHA

Layer 1: the live sandbox (1 hour, refreshed on every interaction)

The hard number is E2B_TIMEOUT_MS = 3_600_000, 3.6 million milliseconds, one hour. The E2B sandbox runtime interprets this as the maximum idle time before it pauses your VM. Pause is not death; the file system stays around, the snapshot stays around, and a future call to Sandbox.connect() with the same sandbox id will resume it.

The clock resets on every reconnect. That is the part most people do not think about. As long as you keep sending prompts, the one-hour timer never gets a chance to fire, because the reconnect path runs await sandbox.setTimeout(E2B_TIMEOUT_MS) and pushes the deadline back by another full hour.

src/core/e2b.ts
reconnectSandbox()

What this means in practice: a casual session where you send a prompt every few minutes will run forever (until you close the tab or hit a real rate limit). A session where you walk away for over an hour will come back to a paused sandbox, which auto-resumes on your next prompt. The auto-resume usually takes a couple of seconds. If E2B has actually killed the underlying VM, the connect-or-create logic falls through to a fresh sandbox and rehydrates the conversation transcript from Firestore.

1h

The clock resets on every reconnect. The retention story is not 'one hour total' but 'one hour of inactivity at a time'.

src/core/e2b.ts, line 337

Layer 2: the pre-warm pool slot (45 minutes max)

The pool exists so that the first prompt of a new session gets a ready sandbox instead of waiting for one to boot. Unclaimed pool entries are tracked in a separate Firestore collection called vm_pool, and they have their own retention clock: POOL_MAX_AGE_MS = 45 * 60 * 1000. That is 45 minutes, deliberately shorter than the 1-hour sandbox timeout so the sweeper can kill the entry before E2B does.

The sweeper itself is a function called cleanupStalePool. It runs as a side effect when prewarmSession is called, which means it is gated on traffic, not on a cron. That is fine in steady state and slightly leaky on a weekend with no visitors, which is acceptable because E2B kills the underlying VMs anyway after their own 1-hour timer.

cleanupStalePool()

Layer 3: the conversation transcript (indefinite, Firestore-backed)

This is the layer most people are actually asking about when they ask about retention. Will the AI remember what I asked it yesterday? In mk0r the answer is yes, as long as the Firestore document for your session is still around.

The transcript itself lives inside the ACP agent process, on the sandbox file system. When the sandbox dies and a fresh one boots, the code calls POST /session/load with the original sessionId and a cwd of /app, and the agent rehydrates its conversation history from disk. The session document in the Firestore collection app_sessions is the durable handle on all of this. As long as it exists, the transcript can be brought back.

The Firestore document is deleted in two situations: the user explicitly resets the session, or an exec probe against a previously-known sandbox returns failure (meaning the sandbox is unreachable beyond what reconnect can fix). The relevant guard sits inside the session-resolve path; on probe failure the doc is deleted with a swallowed-error .delete().catch(() => {}) and the next prompt creates a fresh session.

There is no time-based eviction on the transcript. Idle sessions do not expire on their own. If you come back in two weeks, the transcript is still there.

Layer 4: the per-turn git history (unbounded)

Every prompt that produces a file change runs through commitTurn. That function does a git add -A of the working tree, a git commit, reads git rev-parse HEAD for the new SHA, and pushes it onto an array called session.historyStack. There is no maximum length. The array grows for the life of the session document.

The only place the array is shortened is when you commit on top of an undo. If your activeIndex is mid-stack and you make a new change, the redoable tail gets chopped off by slice(0, activeIndex + 1), then the new SHA is pushed. That is the same behavior git itself uses internally for branch divergence.

commitTurn()

What the sandbox itself loses on a sleep-wake cycle

The four retention layers above are about durability across time. There is a fifth thing worth naming: what survives a sandbox pause and resume, and what does not. The file tree, the git history, and the on-disk ACP transcript all survive untouched. The runtime processes started by /opt/startup.sh do not. Specifically: the Vite dev server on port 5173, Chromium with CDP on 9222, the residential proxy on 3000, and the Xvfb virtual display on :99 are all gone after a wake.

The ACP bridge on 3002 stays alive because it is what receives the wake-up call from outside the VM. So the recovery looks like this: on wake, the server-side code probes port 3000 with a one-second TCP check, sees DOWN, and re-runs startup.sh in the background under nohup. Then it polls port 3000 for up to 30 seconds waiting for it to come back up. The function that does this is ensureVmServices and it runs on every reuse of an existing sandbox. So the user never sees the wake; they just see a slightly slower first prompt after a long idle.

What this means for how you should think about a vibe coding session

If you treat a vibe coding session like a chat window, the only retention that matters to you is the transcript, and that one is indefinite. Close your laptop, come back next week, your context is still there. The sandbox underneath has been recycled four or five times by then, but the rehydration is invisible.

If you treat it like a development environment, you care more about what survives a sandbox restart. The answer is: the file tree, the git history, and the transcript. The dev server processes do not, but they are restarted automatically. Anything you write to the file system in your generated app is durable across pause and resume; anything you keep in memory in the dev server process is not.

If you treat it like a database, the right answer is to not. The sandbox is a runtime, not a data store. If your generated app actually needs to keep user data, it needs to be wired up to a real database (Neon is provisioned for new projects), and that data lives outside any of the four retention clocks above.

Want to talk through your retention story?

If you are choosing between vibe coding tools and the retention model is the deciding factor, a 15-minute call beats a feature comparison. Bring your use case, leave with a recommendation.

Frequently asked questions

What does 'retention limits' actually mean in a vibe coding tool?

It depends which layer you mean. A vibe coding session has at least four pieces of state retained on different clocks: the running sandbox VM (where your dev server lives), the pre-warmed pool slot you got assigned to, the conversation transcript the agent uses to keep context, and the per-turn git history of file snapshots. Each one has a different lifetime, a different eviction trigger, and a different recovery path. Most pages on this topic collapse them into 'how long can I code before I run out of credits', which is actually a rate-limit question, not a retention question. In mk0r the four numbers are: 1 hour for the live sandbox (refreshed on every interaction), 45 minutes for an unclaimed pool slot, indefinite for the transcript via a Firestore-backed reload, and unbounded for the git history.

What is the 1-hour sandbox limit in mk0r and what happens at the end of it?

src/core/e2b.ts line 33 sets E2B_TIMEOUT_MS to 3,600,000 milliseconds, exactly one hour. The E2B sandbox auto-pauses after that window of inactivity. The clock is reset on every reconnect: line 337 calls await sandbox.setTimeout(E2B_TIMEOUT_MS) inside reconnectSandbox, so as long as you keep sending prompts you keep extending the lease. When the hour does run out and the sandbox pauses, your next interaction triggers a Sandbox.connect that auto-resumes it. If E2B has already killed the underlying VM, the connect throws, and the code falls through to creating a new sandbox while preserving the Firestore session document so the conversation transcript is reloaded into the new VM.

Why does the pre-warm pool have a separate 45-minute clock?

Because a pool sandbox that has been sitting unclaimed for too long is about to be killed by E2B's own 1-hour timer, and we would rather hand a user a freshly-warmed VM than one that pauses on them mid-prompt. src/core/e2b.ts line 1896 sets POOL_MAX_AGE_MS to 45 * 60 * 1000. The cleanupStalePool function on line 1927 runs through every entry in the vm_pool Firestore collection, computes now - data.createdAt, and if that exceeds 45 minutes (or the spec hash has drifted from the current build) it kills the underlying sandbox via Sandbox.connect(...).then(sb => sb.kill()) and deletes the doc. This sweep runs as a side effect when prewarmSession is called, so it is gated on traffic, not a cron.

Does the conversation transcript get retained across the sandbox dying?

Yes. The transcript is held by the ACP agent process inside the sandbox, but the session metadata (sessionId, sandboxId, historyStack) lives in the app_sessions Firestore collection (src/core/e2b.ts line 130). When a sandbox dies and a fresh one boots, the code calls /session/load with the original sessionId and the cwd of /app, and the ACP agent rehydrates the transcript from disk. That happens around line 1602. The retention horizon for the transcript is therefore not tied to the sandbox at all, it is tied to whether the Firestore doc is still around. The doc is deleted only on an exec probe failure (line 1057) or by an explicit user action.

How many turns of git history does mk0r keep?

All of them. Every prompt that produces a file change calls commitTurn (src/core/e2b.ts line 1635), which runs git add -A, git commit, and pushes the resulting SHA onto session.historyStack. There is no MAX_HISTORY constant; the array grows unbounded for the life of the session document. The activeIndex pointer lets you undo and redo through the stack. The only place the array is shortened is when you commit on top of an undo: line 1676 truncates the stack with slice(0, activeIndex + 1) so the redoable tail is dropped, then pushes the new SHA. That is the same shape git itself uses internally; nothing fancy.

What gets lost when the sandbox sleeps and wakes?

Background processes started by /opt/startup.sh: the Vite dev server on 5173, Chromium with CDP on 9222, the Bright Data proxy on 3000, and Xvfb on display :99. The ACP bridge on 3002 stays alive because it is what receives the wake-up call. The ensureVmServices function on src/core/e2b.ts line 364 detects this by probing port 3000 inside the VM, and if it gets DOWN it re-runs startup.sh in the background. So 'retention' for runtime processes is zero across a sleep-wake cycle, but the recovery is automatic and takes a few seconds. The on-disk file tree, the git history, and the ACP transcript all survive untouched because the sandbox filesystem is preserved across pause/resume.

How does this compare to other vibe coding tools?

The honest answer is that most vibe coding tools do not publish their retention constants, so the comparison is unfair. What you can observe from the outside: tools that require an account upfront tend to keep your projects on their servers indefinitely (so retention is long, but the eviction trigger is account closure, not idle time). Tools that run in-browser only (one-shot HTML generators) have zero retention by design, because there is nothing to retain. mk0r sits in the middle: an anonymous session creates persistent server-side state, but the runtime cost of keeping a sandbox alive is bounded to roughly one hour of inactivity. The Firestore doc lets the transcript and git history outlive that boundary. If you need help deciding what your specific use case needs, the answer is usually 'whichever tool's retention story matches how you actually use it', which is harder to answer than it sounds.

Where do these numbers come from?

All four constants are pinned to a single file inside the mk0r app: src/core/e2b.ts. E2B_TIMEOUT_MS lives near the top, around line 33. POOL_MAX_AGE_MS lives further down, around line 1896. The behaviors are scattered across the file: sandbox creation in createSandbox, reconnect in reconnectSandbox, services revival in ensureVmServices, the pool sweeper in cleanupStalePool, the per-turn commit in commitTurn, and the transcript reload around the /session/load fetch. The constants are deliberately stable; they have not changed in months.

mk0r.AI app builder
© 2026 mk0r. All rights reserved.