# How to give an AI agent memory that persists between sessions Keep each user's agent in a named sandbox that pauses between sessions. Put any memory that must outlive the sandbox on a volume. **On Runtime, a paused agent keeps everything it had in RAM, its running processes and its files, while you have credit.** A paused agent pays only for storage while it sleeps: $0.08 per GB per 30-day month for the blocks it alone owns, at the rates in force on 26 October 2026. Its variables, open files and background servers are exactly where they were. ## Three kinds of agent memory | Kind | Where it lives on Runtime | Lasts | Costs while idle | | ---------------- | -------------------------------------- | ----------------------------------------------------------- | ---------------------------------------------------- | | Working memory | The paused sandbox's RAM and processes | While you have credit (7 days without credit) | $0.08 per GB-month of owned blocks | | Workspace files | The sandbox's own disk | While the sandbox exists | Included in the paused size | | Long-term memory | A volume attached at a path | Until you delete it, across any sandboxes | About $0.11 per GiB-month | | Checkpoints | Snapshots of the whole machine | while you have credit, up to a year (7 days without credit) | $0.08 per GB-month of stored bytes | Most agents need the first two. Add a volume when a user's memory must survive the sandbox itself. Add a snapshot when you want to return to a known point. ## The short answer: one sandbox per user, paused between visits Name the sandbox after the user or the thread. `getOrCreate` returns the same sandbox every time. If the sandbox is paused, it wakes the sandbox first: ```ts check import { Sandbox } from "withruntime"; export async function agentFor(userId: string) { return Sandbox.getOrCreate(`agent-${userId}`, { idlePauseSeconds: 600, // pause after ten quiet minutes labels: { kind: "assistant", user: userId }, }); } // Monday: the agent loads the user's data and takes a note. const monday = await agentFor("u-42"); await monday.interpreter.run("import pandas as pd\nnotes = ['prefers weekly summaries']"); // Thursday, from another server: the same interpreter, the same variables. const thursday = await agentFor("u-42"); const recall = await thursday.interpreter.run("notes"); console.log(thursday.info.reused, recall.results[0]?.data["text/plain"]); ``` ```python check from withruntime import Sandbox def agent_for(user_id: str): return Sandbox.get_or_create( f"agent-{user_id}", idle_pause_seconds=600, # pause after ten quiet minutes labels={"kind": "assistant", "user": user_id}, ) monday = agent_for("u-42") monday.interpreter.run("import pandas as pd\nnotes = ['prefers weekly summaries']") thursday = agent_for("u-42") recall = thursday.interpreter.run("notes") print(thursday.info.get("reused"), recall["results"][0]["data"]["text/plain"]) ``` The interpreter is a process in the sandbox, so a pause keeps its variables. A data frame that took a minute to load is still loaded next week. Also write the agent's own notes to a file, such as `/workspace/memory.md`. You can then read them with `files.readText`, and they survive a restart. A paid pause is kept while your account has credit. Or it is kept for the 1 to 365 days you set with `POST /v1/sandboxes/{id}:retention`. Each pause replaces the saved state before it ([pricing](/docs/pricing#paused-storage)). Pauses without credit are kept seven days, free. ## Memory that outlives any sandbox A volume is a disk with its own lifetime. Create one per user, attach it to whichever sandbox is serving that user, and keep the agent's long-term store there: ```ts check import { Runtime } from "withruntime"; const runtime = new Runtime(); const memory = await runtime.volumes.create({ sizeMiB: 1024, name: "memory-u-42" }); await using sbx = await runtime.sandboxes.create({ volumes: [{ volumeId: memory.id, path: "/memory" }], labels: { user: "u-42" }, }); await sbx.exec("sudo chown runtime /memory"); await sbx.exec("echo '- prefers weekly summaries' >> /memory/facts.md && sync"); ``` ```python check from withruntime import Runtime runtime = Runtime() memory = runtime.volumes.create(size_mib=1024, name="memory-u-42") with runtime.sandboxes.create(volumes=[{"volume_id": memory["id"], "path": "/memory"}], labels={"user": "u-42"}) as sbx: sbx.exec("sudo chown runtime /memory") sbx.exec("echo '- prefers weekly summaries' >> /memory/facts.md && sync") ``` A volume attaches read-write to one sandbox at a time. That suits one user's memory. It can also attach read-only to any number of sandboxes. Run `sync` after any write that must be kept. A sandbox whose time limit or credit runs out with `"stop"` stops immediately ([storage](/docs/storage)). A volume is charged on its full size from creation, at 153 microdollars per GiB-hour. For a structured store, start the `memory` server from Runtime's MCP catalog inside the sandbox with `sbx.mcp.start([{ id: "memory" }])`. Then give the agent its URL ([MCP servers in a sandbox](/docs/javascript#mcp-servers-in-a-sandbox)). ## Checkpoints you can go back to A snapshot saves the whole machine as it is, including its memory and processes. Take a snapshot before a risky step. If the step goes wrong, start a new sandbox from it: ```ts check import { Runtime, Sandbox } from "withruntime"; const runtime = new Runtime(); const agent = await Sandbox.getOrCreate("agent-u-42", { idlePauseSeconds: 600 }); const before = await agent.snapshot({ name: "u-42-before-import", retentionDays: 30 }); // ... the agent runs a migration that goes badly ... const restored = await runtime.sandboxes.create({ snapshot: before.id }); console.log(restored.id); ``` A snapshot survives the loss of its server once its off-server copy has been checked. Then `backedUp` is `true`. A sandbox with volumes cannot be snapshotted. Keep the two designs separate. Either pause and snapshot the agent's machine, or keep its memory on a volume. ## What persistent agent memory needs | Need | How Runtime covers it | | ----------------------------------- | ---------------------------------------------------------------- | | The same agent for the same user | Names unique per account; `getOrCreate` finds, wakes or makes it | | RAM state kept between sessions | Pause keeps memory and running processes | | Fast return | The next request wakes it, with memory and processes | | Memory apart from the machine | Volumes, attached at any path, kept until deleted | | Undo | Snapshots kept while you have credit; `fork` for live copies | | One user's memory kept from another | A Firecracker microVM with its own kernel per sandbox | | Low cost at rest | No compute while paused; storage by the GB | ## What it costs Take 1,000 users. Each one's 2 vCPU, 4 GiB agent runs 10 hours in a month, using on average 0.1 of a vCPU. The agent is paused the other 710 hours of a 30-day month, with 0.5 GB of its own disk and memory stored: ``` CPU: 1,000 × 10 h × 0.1 vCPU × $0.025 = $25.00 Memory: 1,000 × 10 h × 4 GiB × $0.0075 = $300.00 Paused: 1,000 × 0.5 GB × $0.08 × 710 h / 720 h = $39.44 Total: $364.44 ``` That is about 36 cents per user per month. A 1 GiB memory volume per user would add 153 microdollars × 720 hours, or about $0.11 each ([pricing](/docs/pricing#snapshots-images-and-volumes)). ## Start ```bash no-run npx withruntime sandboxes create --name agent-demo --get-or-create --idle-pause 600 ``` Approve the browser link that the first command prints. Run the same line tomorrow, and it prints the same sandbox's ID. Related: [pause and resume a sandbox](/how-to/pause-and-resume-a-sandbox), [sandbox snapshots](/glossary/sandbox-snapshot), [long-running agents](/use-cases/long-running-agents), [per-user dev environments](/use-cases/per-user-dev-environments). Facts on this page were checked on 26 October 2026.