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:
TypeScriptimport { 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"]);Pythonfrom withruntime import Sandboxdef 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). 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:
TypeScriptimport { 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");Pythonfrom withruntime import Runtimeruntime = 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). 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).
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:
TypeScriptimport { 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:
TextCPU: 1,000 × 10 h × 0.1 vCPU × $0.025 = $25.00Memory: 1,000 × 10 h × 4 GiB × $0.0075 = $300.00Paused: 1,000 × 0.5 GB × $0.08 × 710 h / 720 h = $39.44Total: $364.44About 36 cents a user a month. A 1 GiB memory volume per user would add 153 microdollars × 720 hours, about $0.11 each (pricing).
Start
Terminalnpx withruntime sandbox create --trial --name agent-demo --get-or-create --idle-pause 600Approve 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, sandbox snapshots, long-running agents, per-user dev environments.
Facts on this page were checked on 25 September 2026.