diff --git a/docs.json b/docs.json
index 461b5f10..9bfb799a 100644
--- a/docs.json
+++ b/docs.json
@@ -747,7 +747,8 @@
"group": "AgentKit",
"pages": [
"redis/sdks/agentkit/ai-sdk",
- "redis/sdks/agentkit/eve"
+ "redis/sdks/agentkit/eve",
+ "redis/sdks/agentkit/tanstack-ai"
]
},
"redis/sdks/mcp",
@@ -892,7 +893,8 @@
"group": "AgentKit",
"pages": [
"redis/sdks/agentkit/ai-sdk",
- "redis/sdks/agentkit/eve"
+ "redis/sdks/agentkit/eve",
+ "redis/sdks/agentkit/tanstack-ai"
]
}
]
diff --git a/llms-full.txt b/llms-full.txt
index b55493f8..0979c170 100644
--- a/llms-full.txt
+++ b/llms-full.txt
@@ -86320,6 +86320,482 @@ Two complete `eve` apps live in the AgentKit repo:
+# TanStack AI Persistence, Resumable Streams & Memory with Redis
+Source: https://upstash.com/docs/redis/sdks/agentkit/tanstack-ai
+
+[TanStack AI](https://tanstack.com/ai) defines the contracts for an agent's production state — chat
+persistence, resumable streaming, locks, memory — and ships in-memory implementations that only work
+inside one process. `@upstash/agentkit-tanstack-ai` implements them on Upstash Redis, so they hold
+across serverless instances, page reloads, and devices.
+
+| Import | Plugs into | Feature |
+| --- | --- | --- |
+| `upstashPersistence` | `withPersistence()`, `withGenerationPersistence()` | Transcripts, runs, human-in-the-loop interrupts, metadata, generation jobs, and generated files. |
+| `upstashStream` | `durability` on the response | Resume a stream after a reload, or open the same thread on another device. |
+| `upstashLocks` | `withLocks()` | Distributed locks for TanStack AI middleware, such as its sandbox setup. |
+| `upstashMemory` | `memoryMiddleware()` | Long-term memory ranked in [Redis Search](/docs/redis/search/introduction). |
+| `toolCache`, `rateLimit` | `middleware` | Skip repeated tool calls; throttle users before the model runs. |
+| `createSearchTools` | `tools` | `search` / `aggregate` / `count` over your own documents (RAG). |
+
+```bash
+npm install @upstash/agentkit-tanstack-ai @tanstack/ai
+```
+
+
+ AgentKit reads `UPSTASH_REDIS_REST_URL` / `UPSTASH_REDIS_REST_TOKEN` from the environment by default.
+ Pass a `redis` client to any helper to use a different one.
+
+
+## How to persist TanStack AI chats in Redis
+
+Persistence plugs into TanStack AI's `withPersistence()` middleware, which comes from its persistence
+package:
+
+```bash
+npm install @tanstack/ai-persistence
+```
+
+```ts
+import { chat } from "@tanstack/ai";
+import { withPersistence } from "@tanstack/ai-persistence";
+import { upstashPersistence } from "@upstash/agentkit-tanstack-ai/persistence";
+
+const persistence = upstashPersistence();
+
+chat({ adapter, messages, threadId, middleware: [withPersistence(persistence)] });
+```
+
+This covers every TanStack AI persistence store: `messages`, `runs`, `interrupts`, and `metadata` for
+chats, plus `generationRuns` and `artifacts` for one-shot generation jobs such as images or speech. The
+`blobs` store for generated bytes is added when you pass an Upstash Blob bucket:
+
+```ts
+import { Bucket } from "@upstash/blob";
+
+const persistence = upstashPersistence({ bucket: Bucket.fromEnv() });
+```
+
+Reads `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN` from the environment.
+`Bucket.fromEnv()` reads `UPSTASH_BLOB_TOKEN` from the environment, which is only needed when you
+store generated files.
+
+Runs are indexed by thread, so reconnecting to a live run (`findActiveRun`) is an indexed lookup, newest run first.
+Each write is one command or one Lua script, so concurrent instances cannot interleave it.
+
+
+ ```ts
+ upstashPersistence({
+ redis, // optional: defaults to Redis.fromEnv()
+ prefix: "agentkit:tanstack", // optional: base key prefix
+ messagesTtlSeconds: 60 * 60 * 24 * 30, // optional: expire idle transcripts (default: never)
+ bucket: Bucket.fromEnv(), // optional: Upstash Blob bucket for generated files
+ });
+ ```
+
+ Pass `bucket` to also store the bytes of generated files (images, audio, video) in
+ [Upstash Blob](/docs/blob/overall/quickstart). Without it, there is no `blobs` store.
+
+
+## How to resume a TanStack AI stream after a reload
+
+```ts
+import { chat, toServerSentEventsResponse } from "@tanstack/ai";
+import { upstashStream } from "@upstash/agentkit-tanstack-ai";
+
+export async function POST(request: Request) {
+ const stream = chat({ adapter, messages, threadId });
+ return toServerSentEventsResponse(stream, { durability: { adapter: upstashStream(request) } });
+}
+```
+
+Every chunk is written to a Redis Stream before it is sent. A client that reconnects with
+`Last-Event-ID` (or `?offset`) replays what it missed and keeps following the live run, whichever
+instance serves the request. Without a `Request`, use `upstashStream({ runId, offset })`.
+
+Reads `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN` from the environment.
+
+
+ ```ts
+ upstashStream(request, {
+ ttlSeconds: 86_400, // optional: how long a run stays resumable
+ pollIntervalMs: 150, // optional: how often a caught-up reader checks for new chunks
+ firstChunkDeadlineMs: 2_000, // optional: how long a join waits for a run that has not started
+ });
+ ```
+
+
+## How to use distributed locks with TanStack AI
+
+```ts
+import { withLocks } from "@tanstack/ai/locks";
+import { upstashLocks } from "@upstash/agentkit-tanstack-ai";
+
+chat({ adapter, messages, middleware: [withLocks(upstashLocks()), withSandbox(sandbox)] });
+```
+
+`withLocks` doesn't lock anything by itself. It gives the lock store to later middleware, which lock
+the one step they must not run twice: `withSandbox` uses it so two concurrent requests for a thread
+don't both create a sandbox, and your own middleware can use it through `getLocks(ctx)`. It does not
+serialize whole chat turns. Unlike TanStack's `InMemoryLockStore`, which only works inside one
+process, `upstashLocks()` coordinates across instances.
+
+Each lock is a lease that is renewed while the critical section runs. If the lease is lost, the
+section's `signal` aborts.
+
+Reads `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN` from the environment.
+
+
+ ```ts
+ upstashLocks({
+ leaseMs: 30_000, // optional: lease lifetime without renewal
+ acquireTimeoutMs: 30_000, // optional: how long to wait for a held key
+ retryDelayMs: 100, // optional: delay between attempts
+ });
+ ```
+
+
+## How to add long-term memory to TanStack AI
+
+Memory plugs into TanStack AI's `memoryMiddleware()`, which comes from its memory package:
+
+```bash
+npm install @tanstack/ai-memory
+```
+
+```ts
+import { memoryMiddleware } from "@tanstack/ai-memory";
+import { upstashMemory } from "@upstash/agentkit-tanstack-ai/memory";
+
+chat({
+ adapter,
+ messages,
+ middleware: [
+ memoryMiddleware({
+ adapter: upstashMemory(),
+ // derive these server-side from the session, never from the request body
+ scope: (ctx) => ({ threadId: ctx.threadId, userId: session.userId }),
+ }),
+ ],
+});
+```
+
+Before each turn, the most relevant memories for the user's message are added to the system prompt,
+labelled by where they came from. The model gets a `save_memory` tool for durable facts, and each
+turn's user message is captured too. Memory is per user across threads by default, or per thread when the scope has no `userId`.
+
+Reads `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN` from the environment.
+
+
+ ```ts
+ upstashMemory({
+ topK: 5, // optional: memories injected per turn
+ scopeBy: "user", // optional: "user" (across threads) or "thread"
+ captureUserMessages: true, // optional: store each turn's user message
+ saveTool: true, // optional: offer the save_memory tool
+ waitForIndexing: true, // optional: make a save recallable on the very next turn
+ });
+ ```
+
+
+## How to cache tools and rate limit with TanStack AI
+
+```ts
+import { rateLimit, Ratelimit, toolCache } from "@upstash/agentkit-tanstack-ai";
+
+chat({
+ adapter,
+ messages,
+ tools: [getWeather, sendEmail],
+ middleware: [
+ rateLimit({ limiter: Ratelimit.slidingWindow(10, "60 s"), identifier: userId }),
+ toolCache({ tools: ["get_weather"], userId, ttlSeconds: 600 }),
+ ],
+});
+```
+
+`toolCache` only caches the tools you list — list deterministic, side-effect-free tools only.
+`rateLimit` fails the run with `RateLimitExceededError` before the model is called. For an HTTP 429
+instead, call `createRateLimit({ limiter }).limit(userId)` in your route before `chat()`.
+
+Both middlewares and `createRateLimit` read `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`
+from the environment.
+
+## How to add RAG with TanStack AI
+
+The schema builder `s` comes from `@upstash/redis`:
+
+```bash
+npm install @upstash/redis
+```
+
+```ts
+import { s } from "@upstash/redis";
+import { createSearchTools } from "@upstash/agentkit-tanstack-ai";
+
+const tools = createSearchTools({
+ indexName: "products",
+ schema: s.object({ name: s.string(), price: s.number(), category: s.string().noTokenize() }),
+});
+
+chat({ adapter, messages, tools });
+```
+
+The tool descriptions are generated from the schema, and the index is created on first use.
+
+Reads `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN` from the environment.
+
+## Telemetry
+
+AgentKit adds its package name and version as a header on your Redis client's requests. To turn it
+off, set `UPSTASH_DISABLE_TELEMETRY`, or pass `enableTelemetry: false` to a helper.
+
+
+
+ Source and README for the package.
+
+
+ The framework these backends plug into.
+
+
+
+# TanStack AI Persistence, Resumable Streams & Memory with Redis
+Source: https://upstash.com/docs/redis/sdks/agentkit/tanstack-ai
+
+[TanStack AI](https://tanstack.com/ai) defines the contracts for an agent's production state — chat
+persistence, resumable streaming, locks, memory — and ships in-memory implementations that only work
+inside one process. `@upstash/agentkit-tanstack-ai` implements them on Upstash Redis, so they hold
+across serverless instances, page reloads, and devices.
+
+| Import | Plugs into | Feature |
+| --- | --- | --- |
+| `upstashPersistence` | `withPersistence()`, `withGenerationPersistence()` | Transcripts, runs, human-in-the-loop interrupts, metadata, generation jobs, and generated files. |
+| `upstashStream` | `durability` on the response | Resume a stream after a reload, or open the same thread on another device. |
+| `upstashLocks` | `withLocks()` | Distributed locks for TanStack AI middleware, such as its sandbox setup. |
+| `upstashMemory` | `memoryMiddleware()` | Long-term memory ranked in [Redis Search](/docs/redis/search/introduction). |
+| `toolCache`, `rateLimit` | `middleware` | Skip repeated tool calls; throttle users before the model runs. |
+| `createSearchTools` | `tools` | `search` / `aggregate` / `count` over your own documents (RAG). |
+
+```bash
+npm install @upstash/agentkit-tanstack-ai @tanstack/ai
+```
+
+
+ AgentKit reads `UPSTASH_REDIS_REST_URL` / `UPSTASH_REDIS_REST_TOKEN` from the environment by default.
+ Pass a `redis` client to any helper to use a different one.
+
+
+## How to persist TanStack AI chats in Redis
+
+Persistence plugs into TanStack AI's `withPersistence()` middleware, which comes from its persistence
+package:
+
+```bash
+npm install @tanstack/ai-persistence
+```
+
+```ts
+import { chat } from "@tanstack/ai";
+import { withPersistence } from "@tanstack/ai-persistence";
+import { upstashPersistence } from "@upstash/agentkit-tanstack-ai/persistence";
+
+const persistence = upstashPersistence();
+
+chat({ adapter, messages, threadId, middleware: [withPersistence(persistence)] });
+```
+
+This covers every TanStack AI persistence store: `messages`, `runs`, `interrupts`, and `metadata` for
+chats, plus `generationRuns` and `artifacts` for one-shot generation jobs such as images or speech. The
+`blobs` store for generated bytes is added when you pass an Upstash Blob bucket:
+
+```ts
+import { Bucket } from "@upstash/blob";
+
+const persistence = upstashPersistence({ bucket: Bucket.fromEnv() });
+```
+
+Reads `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN` from the environment.
+`Bucket.fromEnv()` reads `UPSTASH_BLOB_TOKEN` from the environment, which is only needed when you
+store generated files.
+
+Runs are indexed by thread, so reconnecting to a live run (`findActiveRun`) is an indexed lookup, newest run first.
+Each write is one command or one Lua script, so concurrent instances cannot interleave it.
+
+
+ ```ts
+ upstashPersistence({
+ redis, // optional: defaults to Redis.fromEnv()
+ prefix: "agentkit:tanstack", // optional: base key prefix
+ messagesTtlSeconds: 60 * 60 * 24 * 30, // optional: expire idle transcripts (default: never)
+ bucket: Bucket.fromEnv(), // optional: Upstash Blob bucket for generated files
+ });
+ ```
+
+ Pass `bucket` to also store the bytes of generated files (images, audio, video) in
+ [Upstash Blob](/docs/blob/overall/quickstart). Without it, there is no `blobs` store.
+
+
+## How to resume a TanStack AI stream after a reload
+
+```ts
+import { chat, toServerSentEventsResponse } from "@tanstack/ai";
+import { upstashStream } from "@upstash/agentkit-tanstack-ai";
+
+export async function POST(request: Request) {
+ const stream = chat({ adapter, messages, threadId });
+ return toServerSentEventsResponse(stream, { durability: { adapter: upstashStream(request) } });
+}
+```
+
+Every chunk is written to a Redis Stream before it is sent. A client that reconnects with
+`Last-Event-ID` (or `?offset`) replays what it missed and keeps following the live run, whichever
+instance serves the request. Without a `Request`, use `upstashStream({ runId, offset })`.
+
+Reads `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN` from the environment.
+
+
+ ```ts
+ upstashStream(request, {
+ ttlSeconds: 86_400, // optional: how long a run stays resumable
+ pollIntervalMs: 150, // optional: how often a caught-up reader checks for new chunks
+ firstChunkDeadlineMs: 2_000, // optional: how long a join waits for a run that has not started
+ });
+ ```
+
+
+## How to use distributed locks with TanStack AI
+
+```ts
+import { withLocks } from "@tanstack/ai/locks";
+import { upstashLocks } from "@upstash/agentkit-tanstack-ai";
+
+chat({ adapter, messages, middleware: [withLocks(upstashLocks()), withSandbox(sandbox)] });
+```
+
+`withLocks` doesn't lock anything by itself. It gives the lock store to later middleware, which lock
+the one step they must not run twice: `withSandbox` uses it so two concurrent requests for a thread
+don't both create a sandbox, and your own middleware can use it through `getLocks(ctx)`. It does not
+serialize whole chat turns. Unlike TanStack's `InMemoryLockStore`, which only works inside one
+process, `upstashLocks()` coordinates across instances.
+
+Each lock is a lease that is renewed while the critical section runs. If the lease is lost, the
+section's `signal` aborts.
+
+Reads `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN` from the environment.
+
+
+ ```ts
+ upstashLocks({
+ leaseMs: 30_000, // optional: lease lifetime without renewal
+ acquireTimeoutMs: 30_000, // optional: how long to wait for a held key
+ retryDelayMs: 100, // optional: delay between attempts
+ });
+ ```
+
+
+## How to add long-term memory to TanStack AI
+
+Memory plugs into TanStack AI's `memoryMiddleware()`, which comes from its memory package:
+
+```bash
+npm install @tanstack/ai-memory
+```
+
+```ts
+import { memoryMiddleware } from "@tanstack/ai-memory";
+import { upstashMemory } from "@upstash/agentkit-tanstack-ai/memory";
+
+chat({
+ adapter,
+ messages,
+ middleware: [
+ memoryMiddleware({
+ adapter: upstashMemory(),
+ // derive these server-side from the session, never from the request body
+ scope: (ctx) => ({ threadId: ctx.threadId, userId: session.userId }),
+ }),
+ ],
+});
+```
+
+Before each turn, the most relevant memories for the user's message are added to the system prompt,
+labelled by where they came from. The model gets a `save_memory` tool for durable facts, and each
+turn's user message is captured too. Memory is per user across threads by default, or per thread when the scope has no `userId`.
+
+Reads `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN` from the environment.
+
+
+ ```ts
+ upstashMemory({
+ topK: 5, // optional: memories injected per turn
+ scopeBy: "user", // optional: "user" (across threads) or "thread"
+ captureUserMessages: true, // optional: store each turn's user message
+ saveTool: true, // optional: offer the save_memory tool
+ waitForIndexing: true, // optional: make a save recallable on the very next turn
+ });
+ ```
+
+
+## How to cache tools and rate limit with TanStack AI
+
+```ts
+import { rateLimit, Ratelimit, toolCache } from "@upstash/agentkit-tanstack-ai";
+
+chat({
+ adapter,
+ messages,
+ tools: [getWeather, sendEmail],
+ middleware: [
+ rateLimit({ limiter: Ratelimit.slidingWindow(10, "60 s"), identifier: userId }),
+ toolCache({ tools: ["get_weather"], userId, ttlSeconds: 600 }),
+ ],
+});
+```
+
+`toolCache` only caches the tools you list — list deterministic, side-effect-free tools only.
+`rateLimit` fails the run with `RateLimitExceededError` before the model is called. For an HTTP 429
+instead, call `createRateLimit({ limiter }).limit(userId)` in your route before `chat()`.
+
+Both middlewares and `createRateLimit` read `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`
+from the environment.
+
+## How to add RAG with TanStack AI
+
+The schema builder `s` comes from `@upstash/redis`:
+
+```bash
+npm install @upstash/redis
+```
+
+```ts
+import { s } from "@upstash/redis";
+import { createSearchTools } from "@upstash/agentkit-tanstack-ai";
+
+const tools = createSearchTools({
+ indexName: "products",
+ schema: s.object({ name: s.string(), price: s.number(), category: s.string().noTokenize() }),
+});
+
+chat({ adapter, messages, tools });
+```
+
+The tool descriptions are generated from the schema, and the index is created on first use.
+
+Reads `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN` from the environment.
+
+## Telemetry
+
+AgentKit adds its package name and version as a header on your Redis client's requests. To turn it
+off, set `UPSTASH_DISABLE_TELEMETRY`, or pass `enableTelemetry: false` to a helper.
+
+
+
+ Source and README for the package.
+
+
+ The framework these backends plug into.
+
+
+
# Upstash Redis MCP
Source: https://upstash.com/docs/redis/sdks/mcp
diff --git a/llms.txt b/llms.txt
index aad35283..e5bf8ec3 100644
--- a/llms.txt
+++ b/llms.txt
@@ -731,6 +731,8 @@
- [Vercel AI SDK Memory, RAG & Chat History with Redis](https://upstash.com/docs/redis/sdks/agentkit/ai-sdk.md): Add long-term memory, RAG, and chat history to the Vercel AI SDK with Upstash Redis — drop-in tools for generateText and streamText, no separate vector database.
- [Memory, Chat History, RAG, Rate Limiting & Sandboxes for the Vercel Eve Agent Framework](https://upstash.com/docs/redis/sdks/agentkit/eve.md): Add long-term memory, searchable chat history, RAG, rate limiting, tool caching, and sandboxes to Vercel's Eve agent framework with Upstash Redis — no separate vector database.
- [Memory, Chat History, RAG, Rate Limiting & Sandboxes for the Vercel Eve Agent Framework](https://upstash.com/docs/redis/sdks/agentkit/eve.md): Add long-term memory, searchable chat history, RAG, rate limiting, tool caching, and sandboxes to Vercel's Eve agent framework with Upstash Redis — no separate vector database.
+- [TanStack AI Persistence, Resumable Streams & Memory with Redis](https://upstash.com/docs/redis/sdks/agentkit/tanstack-ai.md): Production backends for TanStack AI on Upstash Redis — chat persistence, resumable streams, distributed locks, long-term memory, tool caching, rate limiting, and RAG tools.
+- [TanStack AI Persistence, Resumable Streams & Memory with Redis](https://upstash.com/docs/redis/sdks/agentkit/tanstack-ai.md): Production backends for TanStack AI on Upstash Redis — chat persistence, resumable streams, distributed locks, long-term memory, tool caching, rate limiting, and RAG tools.
- [Upstash Redis MCP](https://upstash.com/docs/redis/sdks/mcp.md)
- [ARCOUNT](https://upstash.com/docs/redis/sdks/py/commands/array/arcount.md): Count the occupied slots in an array.
- [ARDEL](https://upstash.com/docs/redis/sdks/py/commands/array/ardel.md): Delete array values without shifting later indexes.
diff --git a/redis/sdks/agentkit/tanstack-ai.mdx b/redis/sdks/agentkit/tanstack-ai.mdx
new file mode 100644
index 00000000..c8947331
--- /dev/null
+++ b/redis/sdks/agentkit/tanstack-ai.mdx
@@ -0,0 +1,240 @@
+---
+title: "TanStack AI Persistence, Resumable Streams & Memory with Redis"
+sidebarTitle: "TanStack AI"
+description: "Production backends for TanStack AI on Upstash Redis — chat persistence, resumable streams, distributed locks, long-term memory, tool caching, rate limiting, and RAG tools."
+---
+
+[TanStack AI](https://tanstack.com/ai) defines the contracts for an agent's production state — chat
+persistence, resumable streaming, locks, memory — and ships in-memory implementations that only work
+inside one process. `@upstash/agentkit-tanstack-ai` implements them on Upstash Redis, so they hold
+across serverless instances, page reloads, and devices.
+
+| Import | Plugs into | Feature |
+| --- | --- | --- |
+| `upstashPersistence` | `withPersistence()`, `withGenerationPersistence()` | Transcripts, runs, human-in-the-loop interrupts, metadata, generation jobs, and generated files. |
+| `upstashStream` | `durability` on the response | Resume a stream after a reload, or open the same thread on another device. |
+| `upstashLocks` | `withLocks()` | Distributed locks for TanStack AI middleware, such as its sandbox setup. |
+| `upstashMemory` | `memoryMiddleware()` | Long-term memory ranked in [Redis Search](/redis/search/introduction). |
+| `toolCache`, `rateLimit` | `middleware` | Skip repeated tool calls; throttle users before the model runs. |
+| `createSearchTools` | `tools` | `search` / `aggregate` / `count` over your own documents (RAG). |
+
+```bash
+npm install @upstash/agentkit-tanstack-ai @tanstack/ai
+```
+
+
+ AgentKit reads `UPSTASH_REDIS_REST_URL` / `UPSTASH_REDIS_REST_TOKEN` from the environment by default.
+ Pass a `redis` client to any helper to use a different one.
+
+
+## How to persist TanStack AI chats in Redis
+
+Persistence plugs into TanStack AI's `withPersistence()` middleware, which comes from its persistence
+package:
+
+```bash
+npm install @tanstack/ai-persistence
+```
+
+```ts
+import { chat } from "@tanstack/ai";
+import { withPersistence } from "@tanstack/ai-persistence";
+import { upstashPersistence } from "@upstash/agentkit-tanstack-ai/persistence";
+
+const persistence = upstashPersistence();
+
+chat({ adapter, messages, threadId, middleware: [withPersistence(persistence)] });
+```
+
+This covers every TanStack AI persistence store: `messages`, `runs`, `interrupts`, and `metadata` for
+chats, plus `generationRuns` and `artifacts` for one-shot generation jobs such as images or speech. The
+`blobs` store for generated bytes is added when you pass an Upstash Blob bucket:
+
+```ts
+import { Bucket } from "@upstash/blob";
+
+const persistence = upstashPersistence({ bucket: Bucket.fromEnv() });
+```
+
+Reads `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN` from the environment.
+`Bucket.fromEnv()` reads `UPSTASH_BLOB_TOKEN` from the environment, which is only needed when you
+store generated files.
+
+Runs are indexed by thread, so reconnecting to a live run (`findActiveRun`) is an indexed lookup, newest run first.
+Each write is one command or one Lua script, so concurrent instances cannot interleave it.
+
+
+ ```ts
+ upstashPersistence({
+ redis, // optional: defaults to Redis.fromEnv()
+ prefix: "agentkit:tanstack", // optional: base key prefix
+ messagesTtlSeconds: 60 * 60 * 24 * 30, // optional: expire idle transcripts (default: never)
+ bucket: Bucket.fromEnv(), // optional: Upstash Blob bucket for generated files
+ });
+ ```
+
+ Pass `bucket` to also store the bytes of generated files (images, audio, video) in
+ [Upstash Blob](/blob/overall/quickstart). Without it, there is no `blobs` store.
+
+
+## How to resume a TanStack AI stream after a reload
+
+```ts
+import { chat, toServerSentEventsResponse } from "@tanstack/ai";
+import { upstashStream } from "@upstash/agentkit-tanstack-ai";
+
+export async function POST(request: Request) {
+ const stream = chat({ adapter, messages, threadId });
+ return toServerSentEventsResponse(stream, { durability: { adapter: upstashStream(request) } });
+}
+```
+
+Every chunk is written to a Redis Stream before it is sent. A client that reconnects with
+`Last-Event-ID` (or `?offset`) replays what it missed and keeps following the live run, whichever
+instance serves the request. Without a `Request`, use `upstashStream({ runId, offset })`.
+
+Reads `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN` from the environment.
+
+
+ ```ts
+ upstashStream(request, {
+ ttlSeconds: 86_400, // optional: how long a run stays resumable
+ pollIntervalMs: 150, // optional: how often a caught-up reader checks for new chunks
+ firstChunkDeadlineMs: 2_000, // optional: how long a join waits for a run that has not started
+ });
+ ```
+
+
+## How to use distributed locks with TanStack AI
+
+```ts
+import { withLocks } from "@tanstack/ai/locks";
+import { upstashLocks } from "@upstash/agentkit-tanstack-ai";
+
+chat({ adapter, messages, middleware: [withLocks(upstashLocks()), withSandbox(sandbox)] });
+```
+
+`withLocks` doesn't lock anything by itself. It gives the lock store to later middleware, which lock
+the one step they must not run twice: `withSandbox` uses it so two concurrent requests for a thread
+don't both create a sandbox, and your own middleware can use it through `getLocks(ctx)`. It does not
+serialize whole chat turns. Unlike TanStack's `InMemoryLockStore`, which only works inside one
+process, `upstashLocks()` coordinates across instances.
+
+Each lock is a lease that is renewed while the critical section runs. If the lease is lost, the
+section's `signal` aborts.
+
+Reads `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN` from the environment.
+
+
+ ```ts
+ upstashLocks({
+ leaseMs: 30_000, // optional: lease lifetime without renewal
+ acquireTimeoutMs: 30_000, // optional: how long to wait for a held key
+ retryDelayMs: 100, // optional: delay between attempts
+ });
+ ```
+
+
+## How to add long-term memory to TanStack AI
+
+Memory plugs into TanStack AI's `memoryMiddleware()`, which comes from its memory package:
+
+```bash
+npm install @tanstack/ai-memory
+```
+
+```ts
+import { memoryMiddleware } from "@tanstack/ai-memory";
+import { upstashMemory } from "@upstash/agentkit-tanstack-ai/memory";
+
+chat({
+ adapter,
+ messages,
+ middleware: [
+ memoryMiddleware({
+ adapter: upstashMemory(),
+ // derive these server-side from the session, never from the request body
+ scope: (ctx) => ({ threadId: ctx.threadId, userId: session.userId }),
+ }),
+ ],
+});
+```
+
+Before each turn, the most relevant memories for the user's message are added to the system prompt,
+labelled by where they came from. The model gets a `save_memory` tool for durable facts, and each
+turn's user message is captured too. Memory is per user across threads by default, or per thread when the scope has no `userId`.
+
+Reads `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN` from the environment.
+
+
+ ```ts
+ upstashMemory({
+ topK: 5, // optional: memories injected per turn
+ scopeBy: "user", // optional: "user" (across threads) or "thread"
+ captureUserMessages: true, // optional: store each turn's user message
+ saveTool: true, // optional: offer the save_memory tool
+ waitForIndexing: true, // optional: make a save recallable on the very next turn
+ });
+ ```
+
+
+## How to cache tools and rate limit with TanStack AI
+
+```ts
+import { rateLimit, Ratelimit, toolCache } from "@upstash/agentkit-tanstack-ai";
+
+chat({
+ adapter,
+ messages,
+ tools: [getWeather, sendEmail],
+ middleware: [
+ rateLimit({ limiter: Ratelimit.slidingWindow(10, "60 s"), identifier: userId }),
+ toolCache({ tools: ["get_weather"], userId, ttlSeconds: 600 }),
+ ],
+});
+```
+
+`toolCache` only caches the tools you list — list deterministic, side-effect-free tools only.
+`rateLimit` fails the run with `RateLimitExceededError` before the model is called. For an HTTP 429
+instead, call `createRateLimit({ limiter }).limit(userId)` in your route before `chat()`.
+
+Both middlewares and `createRateLimit` read `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`
+from the environment.
+
+## How to add RAG with TanStack AI
+
+The schema builder `s` comes from `@upstash/redis`:
+
+```bash
+npm install @upstash/redis
+```
+
+```ts
+import { s } from "@upstash/redis";
+import { createSearchTools } from "@upstash/agentkit-tanstack-ai";
+
+const tools = createSearchTools({
+ indexName: "products",
+ schema: s.object({ name: s.string(), price: s.number(), category: s.string().noTokenize() }),
+});
+
+chat({ adapter, messages, tools });
+```
+
+The tool descriptions are generated from the schema, and the index is created on first use.
+
+Reads `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN` from the environment.
+
+## Telemetry
+
+AgentKit adds its package name and version as a header on your Redis client's requests. To turn it
+off, set `UPSTASH_DISABLE_TELEMETRY`, or pass `enableTelemetry: false` to a helper.
+
+
+
+ Source and README for the package.
+
+
+ The framework these backends plug into.
+
+