---
title: Changelog
description: Breaking changes, new APIs, and migration guide for the latest release.
type: overview
summary: File-based agent routing for Next.js. Agents are discovered from the filesystem and served automatically.
---

# Changelog



# v0.5.0

## Resolvable agent options

Agent options (`model`, `system`, `activeTools`, `generation`, `skills`) now accept `Resolvable<T>` — a static value or a function evaluated fresh on every LLM step. This replaces send-level overrides and session-stored config.

```ts
const myAgent = agent("my-agent", {
  model: "anthropic/claude-sonnet-4-6",
  system: async ({ context }) => {
    const rules = await fetchRules(context.projectId);
    return `You are a coding assistant.\n${rules}`;
  },
  activeTools: ({ messages }) => {
    const inPlanMode = messages.some(m =>
      m.parts.some(p => "type" in p && p.type === "tool-EnterPlanMode")
    );
    return inPlanMode ? ["Read", "Grep", "List"] : ["Read", "Write", "Edit", "Grep", "List", "Bash"];
  },
});
```

Resolvable functions receive `{ context, sessionId, sandbox, messages }` — the same shape as `ToolContext`.

## Simplified session model

Session is now minimal: `{ id, sandboxId, lastMessageId, createdAt, updatedAt }`. Runtime config (model, system, activeTools, generation, skills) is no longer stored on the session — it's resolved from agent options each step.

* **`session.update()`** is scoped to `{ sandboxId?, lastMessageId? }`. To change model, system, or tools dynamically, use resolvable agent options with context.
* **`SendOptions`** no longer accepts model, system, activeTools, generation, or skills overrides. Pass context instead and use resolvable agent options to derive config.

### Before

```ts
await session.send("hello", {
  model: "anthropic/claude-opus-4.6",
  system: "Be concise.",
  activeTools: ["Read", "Write"],
});
```

### After

```ts
const myAgent = agent("my-agent", {
  model: ({ context }) => context.useAdvanced
    ? "anthropic/claude-opus-4.6"
    : "anthropic/claude-sonnet-4-6",
  system: ({ context }) => context.systemPrompt,
  activeTools: ({ context }) => context.tools,
});

await session.send("hello", {
  context: { useAdvanced: true, systemPrompt: "Be concise.", tools: ["Read", "Write"] },
});
```

## ToolContext changes

`ToolContext` is now `ResolvableArgs` — the same shape used by resolvable option functions:

| Before            | After                           |
| ----------------- | ------------------------------- |
| `context.session` | `context.sessionId`             |
| `context.storage` | *(removed)*                     |
| `context.sandbox` | `context.sandbox` *(unchanged)* |
| `context.context` | `context.context` *(unchanged)* |
| —                 | `context.messages` *(new)*      |

## Other changes

* **`Resolvable`, `ResolvableArgs` exports** — New types for building resolvable option patterns.
* **`activeTools` accepts `readonly` arrays** — `as const` tool lists work without casts.
* **`localStorage()` auto-gitignore** — Emits a `.gitignore` in the storage directory on first write to prevent session data from being tracked by git.
* **`SystemPromptFn`, `SystemPromptInput` removed** — System is now `Resolvable<string | string[] | undefined>`. Arrays are joined with newlines; falsy values are filtered.
* **Default sandbox setup options** — Sandbox bindings accept default `setup` options applied on first sandbox creation.
* **`session.sandbox` handle** — Access the session's sandbox outside of tool execution without knowing the sandbox ID.
* **Durable message metadata** — Type-safe metadata on messages via `agent<Metadata>()` generic.

***

# v0.4.0

## KV2 storage auto-fallback

When no `storage` is configured, the agent automatically falls back to KV2-backed storage when available.

```ts
// explicit storage — nothing changes
const myAgent = agent("my-agent", {
  storage: myCustomStorage(),
});

// no storage — auto-falls back to KV2
const myAgent = agent("my-agent", {});
```

* `BLOB_READ_WRITE_TOKEN` must be present in the environment for it to fallback to KV2
* Keys are namespaced under the agent name (e.g. `"my-agent/"`)
* `@vercel/kv2` is dynamically imported on first storage access
* Uses `"use step"` for workflow durability
* Falls back to `localStorage()` when the token is not set (e.g. local dev)

***

# v0.3.0

Redesigned storage and workflow. Storage is now a handler map you provide directly. Workflow is fully opt-in.

## Storage

Storage backends (`{ type: "vercel" | "local" | "custom" }`) are replaced by a `storage()` function with inline handlers.

### Before

```ts
agent({ storage: { type: "vercel" } })
```

### After

Storage is a function that receives a `store` and dispatches to your handlers. To add workflow durability, add `"use step"` at the top — that's the only difference.

```ts
agent("my-agent", {
  async storage(store) {
    "use step"; // remove this line if you don't use workflow

    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, part, sandbox handlers
    });
  },
})
```

For development, use the built-in `localStorage()`. For Vercel deployments, use `@vercel/kv2` (see the vade app for a full example).

## Session

`session()` is now synchronous. `send()` accepts the message directly and returns a `SendResult`.

### Before

```ts
const session = await myAgent.session(chatId);
await session.send({ input: message });
const stream = await session.stream();
```

### After

```ts
const session = myAgent.session(chatId);
await session.send(message, opts);
const stream = await session.stream();
```

`send()` returns `SendResult { assistantMessageId, done }` instead of `void | Error`.

## Workflow

Workflow is no longer always-on. Everything works without it. To add durability, define a workflow function and start it yourself.

### Before

The `withAgent` Next.js plugin in `next.config.ts` automatically wired workflow durability behind the scenes. You didn't opt in — it was always on.

```ts
// next.config.ts
import { withAgent } from "experimental-agent/next";
export default withAgent(nextConfig);

// route.ts — workflow was implicit, handled by the plugin
const session = await myAgent.session(chatId);
await session.send({ input: message });
const stream = await session.stream();
```

### After

```ts
// workflow.ts — you define the workflow function
export async function agentWorkflow(
  sessionId: string,
  ...args: SessionSendArgs<typeof myAgent>
) {
  "use workflow";
  return await myAgent.session(sessionId).send(...args);
}

// route.ts — you start it and stream from the result
import { start } from "workflow/api";

const result = await start(agentWorkflow, [sessionId, message, opts]);
const stream = await session.stream(result);
return createUIMessageStreamResponse({ stream });
```

`send()` uses `"use step"` internally, which is a no-op outside a workflow. No code changes needed in the agent itself.

## Streaming

`session.stream()` now accepts an optional `WorkflowRunLike` to stream from a workflow result.

### Before

```ts
const stream = await session.stream();
```

### After

```ts
// In-memory reconnect (no workflow)
const stream = await session.stream();

// Stream from a workflow result
const result = await start(agentWorkflow, [...]);
const stream = await session.stream(result);
```

## All changes

**New APIs:**

* `StorageHandlers` — flat map of handler functions (`"session.get"`, `"message.set"`, etc.) ([`27ca29d`](https://github.com/vercel-labs/agent-sdk/commit/27ca29d), [`0033ac2`](https://github.com/vercel-labs/agent-sdk/commit/0033ac2))
* `StorageStepFunction` — `(store: StorageStep) => Promise<any>` with `"use step"` for workflow ([`748b912`](https://github.com/vercel-labs/agent-sdk/commit/748b912))
* `StorageStep` class — serializable across workflow boundary, dispatches via `store.on(handlers)` ([`748b912`](https://github.com/vercel-labs/agent-sdk/commit/748b912))
* `localStorage()` — built-in filesystem-backed `StorageHandlers` ([`0033ac2`](https://github.com/vercel-labs/agent-sdk/commit/0033ac2))
* `SendResult { assistantMessageId, done }` — return type of `send()` ([`27ca29d`](https://github.com/vercel-labs/agent-sdk/commit/27ca29d))
* `session.stream(result?)` — accepts `WorkflowRunLike` for workflow streaming ([`7b2e370`](https://github.com/vercel-labs/agent-sdk/commit/7b2e370), [`5ff9743`](https://github.com/vercel-labs/agent-sdk/commit/5ff9743))
* `session.history()` — returns conversation history from storage ([`27ca29d`](https://github.com/vercel-labs/agent-sdk/commit/27ca29d), [`3827005`](https://github.com/vercel-labs/agent-sdk/commit/3827005))
* `agent(name, opts)` — name is now a required first positional argument ([`27ca29d`](https://github.com/vercel-labs/agent-sdk/commit/27ca29d))

**Changed APIs:**

* `session()` is synchronous (no `await`) ([`27ca29d`](https://github.com/vercel-labs/agent-sdk/commit/27ca29d))
* `send(input, opts?)` — input is a string, UIMessage, parts array, or `{ type: "approval" | "message", ... }`; options include `interruptIfStreaming`, `context`, and session updates ([`27ca29d`](https://github.com/vercel-labs/agent-sdk/commit/27ca29d), [`3827005`](https://github.com/vercel-labs/agent-sdk/commit/3827005))
* `session.stream()` returns `ReadableStream<UIMessageChunk>` (not `AgentStream`) ([`7b2e370`](https://github.com/vercel-labs/agent-sdk/commit/7b2e370))
* Vercel storage: use `@vercel/kv2` to implement your handlers ([`0033ac2`](https://github.com/vercel-labs/agent-sdk/commit/0033ac2))

**Removed APIs:**

* `StorageConfig` type (`{ type: "vercel" | "local" | "custom" }`) ([`0033ac2`](https://github.com/vercel-labs/agent-sdk/commit/0033ac2))
* `handleStorageRpc` and `Handlers` (old RPC type) ([`0033ac2`](https://github.com/vercel-labs/agent-sdk/commit/0033ac2))
* `session.ui()` — use `session.history()` instead ([`3827005`](https://github.com/vercel-labs/agent-sdk/commit/3827005))
* `session.tag.*` and `tagsSchema` ([`27ca29d`](https://github.com/vercel-labs/agent-sdk/commit/27ca29d))
* `session.resolveApproval()` — use `send({ type: "approval", ... })` instead ([`3827005`](https://github.com/vercel-labs/agent-sdk/commit/3827005))
* `myAgent.storage.*` — direct storage access from the agent ([`0033ac2`](https://github.com/vercel-labs/agent-sdk/commit/0033ac2))
* `myAgent.handler()` and `myAgent.rpc` ([`0033ac2`](https://github.com/vercel-labs/agent-sdk/commit/0033ac2))
* `workflowSend()` and `workflowInput()` on session ([`e643d88`](https://github.com/vercel-labs/agent-sdk/commit/e643d88))
* `withAgent` next.config.ts wrapper ([`27ca29d`](https://github.com/vercel-labs/agent-sdk/commit/27ca29d))
* `apps/storage` (Vercel hosted storage service) ([`0033ac2`](https://github.com/vercel-labs/agent-sdk/commit/0033ac2))
* RPC dispatch layer ([`0033ac2`](https://github.com/vercel-labs/agent-sdk/commit/0033ac2))
