# How to give an AI agent memory that persists between sessions Keep each user's agent in a named sandbox that pauses between sessions, and put memory that must outlive it on a volume. **On Runtime a paused agent keeps everything it had in RAM, its running processes and its files, for 1 to 365 days**, and pays only storage while it sleeps: $0.08 per GB per 30-day month for the blocks it alone owns, at the rates in force on 25 September 2026. The next request wakes it, usually in about half a second, with its variables, open files and background servers 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 | 1 to 365 days per pause (30 by default, paid) | $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 | 1 to 365 days (7 by default) | $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, and a snapshot when you want to go back to a known point. ## The short answer: one sandbox per user, paused between visits Name the sandbox after the user or the thread. `getOrCreate` answers the same sandbox every time, waking it if it paused: ```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. Write the agent's own notes to a file too, such as `/workspace/memory.md`, so they are readable with `files.readText` and survive a restart. A paid pause is kept 30 days unless you set 1 to 365 with `POST /v1/sandboxes/{id}:retention`, and each pause replaces the saved state before it ([pricing](/docs/pricing#paused-storage)). Trial pauses 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 them, 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, which suits one user's memory, or read-only to any number. Run `sync` after writes that must be kept: a sandbox whose lease runs out stops at once ([storage](/docs/storage)). A volume is charged on its full size from creation, 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" }])` and 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, memory and processes included. Take one before a risky step, and start a new sandbox from it if the step goes wrong: ```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 is copied off its server as soon as it is taken, so it survives the loss of that server. A sandbox with volumes cannot be snapshotted, so keep the two designs apart: 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 | Wake on request, usually about half a second | | Memory apart from the machine | Volumes, attached at any path, kept until deleted | | Undo | Snapshots with 1 to 365 days of retention; `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, and 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 ``` About 36 cents a user a month. A 1 GiB memory volume per user would add 153 microdollars × 720 hours, about $0.11 each ([pricing](/docs/pricing#snapshots-images-and-volumes)). ## Start ```bash no-run npx withruntime sandbox create --trial --name agent-demo --get-or-create --idle-pause 600 ``` Approve the browser link 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 25 September 2026.