---
title: '0.17'
description: Redis stores need a fresh key prefix, and agents are served with `agent()`.
---

## Redis: a fresh `keyPrefix`

```ts server/store.ts
import { redis } from 'experimental-a2/store-redis'

export const store = redis({
  url: process.env.REDIS_URL,
  // a prefix no earlier release wrote under:
  keyPrefix: 'a2-017',
})
```

Both Redis stores change how they keep event ids and per-event metadata: one
key per event id instead of one shared hash, and metadata in hashes of 1,000
events. 0.17 no longer reads the shared id hash, and earlier releases can't
see what 0.17 writes, so on a shared prefix a retry on either side can append
a duplicate event, re-run work, or stall a lane. Switch to a fresh `keyPrefix`
when you upgrade, and don't roll back to an earlier release on a prefix 0.17
has written. Vercel Queues keeps running recovery and scheduled work on the
deployment that published it for up to seven days, so earlier deployments keep
touching their sessions after you upgrade.

## Before you clear sessions

0.17 adds [`session.clear()`](/contract/sessions#clearing-a-session). Upgrade
every instance before you clear any session: an earlier release ignores the
clear and can run cleared events in the new lifetime. Upgrade every scheduler
route before you schedule clears, and don't roll back while clear tasks are
pending.

## Vercel Queues

`vercelQueues({ scheduledAppends })` is replaced by
`deploymentId?: string | null`: leave it out to deliver to the current
deployment, pass a deployment ID to target one, or pass `null` for
deploymentless delivery. It applies to every task, recovery included, so
`null` makes recovery deploymentless too, unlike `'deploymentless'` did.
Changing it doesn't move tasks already queued. A custom `QueueTransport.send`
no longer takes a fourth routing argument. See
[Deployments](/schedulers/vercel-queues#deployments).

## Agents

| Before | 0.17 |
| --- | --- |
| `createAgentServer({ agent: definition, ... })` | `agent({ name, ... })`, or `agent({ contract, ... })` with a shared contract |
| `agent()` from `experimental-a2/ai` | `agentContract()` |
| `createReducer()`, `reducerName` | `agentContract({ reducer })` |
| `createHandlers()`, `authorizeAgentIngress` | removed: pass `handlers` and `events` to `agent()` |
| `handlerContext(agent)` | `handlerContext()`, or `handlerContext(contract)` |
| `request` in `generate` and `messageMetadata` | `event` |
| `requestId` | `event.id` |
| `responseMessageId` | `event.payload.responseMessageId` |

- **`agent({ contract })` takes only an `agentContract()`.** Any other contract
  throws. A fully custom contract is core A2, served by `createServer()`.
- **Upgrade clients with the server.** A client from before 0.17 ignores the
  new shape of streamed progress, so a response's output doesn't appear in it
  until it reloads.
- **Custom telemetry recorders** must accept the new span names: core claim
  and commit spans, and `a2.ai.*`.

0.21 later renames `handlers` to `consumers` and `handlerContext()` to
`consumerContext()`; see [0.21](/upgrades/0-21).
