@upstash/agentkit-tanstack-ai stores chat state in Upstash Redis. Every serverless instance then reads the same messages, streams, and locks. Upstash maintains it.
| TanStack AI part | Upstash adapter |
|---|---|
| withPersistence() / withGenerationPersistence() stores | upstashPersistence() |
| StreamDurability for resumable streams | upstashStream() |
| LockStore for withLocks() | upstashLocks() |
| MemoryAdapter for memoryMiddleware() | upstashMemory() |
The client is @upstash/redis. It talks HTTP, so it works on edge and serverless runtimes with no connection pool.
npm i @upstash/agentkit-tanstack-ai @tanstack/aipnpm add @upstash/agentkit-tanstack-ai @tanstack/aiyarn add @upstash/agentkit-tanstack-ai @tanstack/aibun add @upstash/agentkit-tanstack-ai @tanstack/aiPersistence and memory have their own entry points. Also install the TanStack package for each one that you use:
Put the credentials of your Upstash Redis database in the environment of your server:
UPSTASH_REDIS_REST_URL=https://your-db.upstash.io
UPSTASH_REDIS_REST_TOKEN=your-tokenEvery factory reads these two variables. You can also pass your own redis client.
upstashPersistence() gives you the stores. Pass it to withPersistence, the same as any persistence adapter:
import { chat } from '@tanstack/ai'
import { openaiText } from '@tanstack/ai-openai'
import { withPersistence } from '@tanstack/ai-persistence'
import { upstashPersistence } from '@upstash/agentkit-tanstack-ai/persistence'
const persistence = upstashPersistence()
export const stream = chat({
adapter: openaiText('gpt-5.5'),
messages: [{ role: 'user', content: 'hi' }],
threadId: 'support-chat',
middleware: [withPersistence(persistence)],
})The chat history of support-chat is now in Redis, so every instance can load it. reconstructChat(persistence, request, ...) works with no change, so the GET handler from the Persistence Overview needs no other edits.
upstashPersistence() gives these stores:
To keep generated files, pass an Upstash Blob bucket. It adds a blobs store:
import { Bucket } from '@upstash/blob'
import { upstashPersistence } from '@upstash/agentkit-tanstack-ai/persistence'
const persistence = upstashPersistence({ bucket: Bucket.fromEnv() })upstashStream(request) is a StreamDurability adapter. A client that reconnects gets the chunks it missed, then follows the live run, on any instance:
import {
chat,
chatParamsFromRequest,
resumeServerSentEventsResponse,
toServerSentEventsResponse,
} from '@tanstack/ai'
import { openaiText } from '@tanstack/ai-openai'
import { upstashStream } from '@upstash/agentkit-tanstack-ai'
export async function POST(request: Request) {
const params = await chatParamsFromRequest(request)
const stream = chat({
adapter: openaiText('gpt-5.5'),
messages: params.messages,
threadId: params.threadId,
runId: params.runId,
})
return toServerSentEventsResponse(stream, {
durability: { adapter: upstashStream(request) },
})
}
export function GET(request: Request) {
return resumeServerSentEventsResponse({ adapter: upstashStream(request) })
}See Resumable Streams.
upstashLocks() is a distributed LockStore. Give it to withLocks in place of InMemoryLockStore. Then withSandbox (or your own middleware, through getLocks) takes the lock across instances:
import { chat } from '@tanstack/ai'
import { withLocks } from '@tanstack/ai/locks'
import { openaiText } from '@tanstack/ai-openai'
import { upstashLocks } from '@upstash/agentkit-tanstack-ai'
export const stream = chat({
adapter: openaiText('gpt-5.5'),
messages: [{ role: 'user', content: 'hi' }],
threadId: 'thread-1',
middleware: [withLocks(upstashLocks())],
})upstashMemory() is a MemoryAdapter on Upstash Redis Search. Recall is a BM25 full-text query in Redis, so it does not scan every record on each turn:
import { chat } from '@tanstack/ai'
import { memoryMiddleware } from '@tanstack/ai-memory'
import { openaiText } from '@tanstack/ai-openai'
import { upstashMemory } from '@upstash/agentkit-tanstack-ai/memory'
declare const session: { userId: string }
export const stream = chat({
adapter: openaiText('gpt-5.5'),
messages: [{ role: 'user', content: 'hi' }],
threadId: 'support-chat',
middleware: [
memoryMiddleware({
adapter: upstashMemory(),
// Derive the user from your session, never from the request body.
scope: (ctx) => ({ threadId: ctx.threadId, userId: session.userId }),
}),
],
})See Memory.
| Option | Default | What it does |
|---|---|---|
| redis | Redis.fromEnv() | Your @upstash/redis client. |
| prefix | 'agentkit:tanstack' | The key namespace. |
| messagesTtlSeconds | none | Expire idle transcripts. Runs, interrupts, and metadata never expire. |
| bucket | none | An Upstash Blob bucket for the blobs store. |
| Option | Default | What it does |
|---|---|---|
| ttlSeconds | 86400 | How long a run stays resumable after its last chunk. |
| pollIntervalMs | 150 | The poll interval for the live tail. The REST API has no blocking reads. |
| firstChunkDeadlineMs | 2000 | How long a reader from the start waits for a run that has no output yet. |
| Option | Default | What it does |
|---|---|---|
| scopeBy | 'user' | Share memories across the threads of a user, or keep them per 'thread'. |
| saveTool | true | Give the model a save_memory tool for durable facts. |
| captureUserMessages | true | Store the user message of each turn. |
| waitForIndexing | true | Wait for the index on save, so the next turn can recall a memory. |
For every option and feature, see the AgentKit for TanStack AI docs. The source and the issues are on GitHub.