Concepts

Storage

How the agent persists sessions, messages, parts, and sandbox records. Bring your own database or use the built-in filesystem handler.

The agent uses storage to maintain conversation context, resume workflows, and correlate tool execution across requests.

How It Works

Storage is a function you pass to the agent. It receives a store object and dispatches it to your handlers via store.on().

src/agent.ts
import { agent } from "experimental-agent";

export const myAgent = agent("my-agent", {
  model: "anthropic/claude-sonnet-4-6",
  async storage(store) {
    return await store.on({
      "session.get": async ({ id }) => await db.sessions.findById(id),
      "session.set": async ({ id, value }) => await db.sessions.upsert(id, value),
      "session.update": async ({ id, updates }) => await db.sessions.update(id, updates),

      "message.get": async ({ id }) => await db.messages.findById(id),
      "message.set": async ({ id, value }) => await db.messages.upsert(id, value),
      "message.update": async ({ id, updates }) => await db.messages.update(id, updates),
      "message.listBySession": async ({ sessionId }) => await db.messages.findBySession(sessionId),

      "part.get": async ({ id }) => await db.parts.findById(id),
      "part.set": async ({ id, value }) => await db.parts.upsert(id, value),
      "part.listBySession": async ({ sessionId }) => await db.parts.findBySession(sessionId),

      "sandbox.get": async ({ id }) => await db.sandboxes.findById(id),
      "sandbox.set": async ({ id, value }) => await db.sandboxes.upsert(id, value),
      "sandbox.update": async ({ id, updates }) => await db.sandboxes.update(id, updates),

      "setup.get": async ({ id }) => await db.setups.findById(id),
      "setup.set": async ({ id, value }) => await db.setups.upsert(id, value),
    });
  },
});

To make this work with Vercel Workflow, add "use step" at the top of the function:

src/agent.ts
export const myAgent = agent("my-agent", {
  model: "anthropic/claude-sonnet-4-6",
  async storage(store) {
    "use step";

    return await store.on({
      "session.get": async ({ id }) => await db.sessions.findById(id),
      // ... same handlers
    });
  },
});

That's it. The "use step" directive tells the workflow bundler to extract this function as a retryable step, so storage calls survive workflow replay. Without it, everything works the same — just without durability.

Built-in: localStorage()

For development, the SDK includes localStorage() — a filesystem-backed storage function. Data is stored as JSON files in .agent/ by default.

import { localStorage } from "experimental-agent/storage";

agent("my-agent", {
  storage: localStorage(),
})

agent("my-agent", {
  storage: localStorage({ dir: ".my-data" }),
})

If you omit storage entirely, the SDK falls back to localStorage() with a warning.

What Gets Stored

EntityPurpose
SessionConversation identity, model config, system prompt, generation options, last message ID.
MessageUser and assistant turns. Role, usage, timestamps, workflow run ID, optional app-defined metadata.
PartIndividual content parts: text chunks, tool calls, tool results.
SandboxSandbox ID, config, provider metadata.

Message Metadata

Messages support an optional metadata field for application-defined annotations that need to live alongside the message in storage. This is useful for tagging messages with a kind or type (e.g. synthetic context injections, imported messages, UI rendering hints) without encoding that information in the message content itself.

await session.send({
  parts: [{ type: "text", text: "User's current project context..." }],
  metadata: { type: "user-context" },
});

Metadata is opaque to the agent — it round-trips through storage unchanged and is returned by session.history(). See Message Metadata for type-safe usage.

Design Philosophy

Storage holds what the agent needs to function, plus optional message-level metadata for your application. Broader concerns — user ownership, access control, titles — belong in your app's database. Use the session ID as the join key.

┌──────────────────┐     session ID     ┌──────────────────┐
│  Your Database   │◄──────────────────►│  Agent Storage   │
│                  │                    │                  │
│  userId          │                    │  Session         │
│  title           │                    │  Message (+meta) │
│  access control  │                    │  Part            │
│                  │                    │  Sandbox         │
└──────────────────┘                    └──────────────────┘

Next Steps

  • Custom Storage — Full guide with @vercel/kv2 example
  • Sessions — How sessions use storage for persistence
  • Sandbox — Sandbox records and lifecycle