TanStack AI defines the contracts for an agent’s production state — chat
persistence, session history, stored credentials, 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, generated files, the session index, and the session log. |
upstashCredentials | CredentialStore (@tanstack/ai/byok) | Provider API keys and OAuth tokens that users or organizations saved, optionally encrypted at rest. |
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. |
toolCache, rateLimit | middleware | Skip repeated tool calls; throttle users before the model runs. |
createSearchTools | tools | search / aggregate / count over your own documents (RAG). |
Version 0.3.0 and later need TanStack AI 1.0 or later: @tanstack/ai and @tanstack/ai-persistence must be
>=1.0.0. If you are still on TanStack AI 0.x, stay on @upstash/agentkit-tanstack-ai@0.2.0.
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:
This covers every TanStack AI persistence store: messages, runs, interrupts, and metadata for
chats, generationRuns and artifacts for one-shot generation jobs such as images or speech, and the
sessions and log stores described below. The
blobs store for generated bytes is added when you pass an Upstash Blob bucket:
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.
Options
Pass bucket to also store the bytes of generated files (images, audio, video) in
Upstash Blob. Without it, there is no blobs store.
How to keep a session index in Redis#
upstashPersistence() also returns a sessions store, TanStack AI’s SessionIndexStore. It holds one
entry per (tenantId, threadId) that a “your chats” list reads: members, title, harness, token usage,
pinned and archived flags, and your own metadata. It holds no messages, which stay in the other stores.
listis newest-first per tenant, and filters bymemberId,search(title, case-insensitive),harness,parentThreadId,metadata, andwithArchived. Pass the returnedcursorback for the next page.upsertreplaces the whole entry. To change one field safely from several machines, read the entry withgetVersionedand write the changed copy withupsertIf, which only writes at the revision you read.deleteremoves only the entry, not the thread’s data.
To use the index on its own, import upstashSessionIndex(redis, prefix) from
@upstash/agentkit-tanstack-ai/persistence.
How to keep the session log in Redis#
upstashPersistence() also returns a log store, TanStack AI’s LogStore: the append-only log a host
keeps for a session tree (events, transcript, inputs, and tool steps), with what several machines need to
share it safely.
- Append:
appendwrites a batch at the next position, together with the pending inputs it consumes, in one atomic step. A second writer at the same position gets aLogConflictErrorand nothing is written. One call takes at most 10,000 records. - Ownership:
acquireOwnership,renewOwnership, andreleaseOwnershipgive one host a lease with an epoch. A host that lost the log gets anOwnershipLostErroron its next fenced write. Lease expiry uses the Redis server clock, so your machines need no clock sync. - Input lane:
offerInputlets any machine queue an input, andlistLogsWithoutOwnerfinds logs whose owner expired, that have pending inputs, or whose wake time is due.markHostAliveandpinToWorkerskeep a log on one group of workers. - Snapshots:
putSnapshotandtruncatekeep a log short. Reading before the truncation point throwsLogTruncatedError. - Subscriptions:
subscribecalls your listener right after writes through the same store, and polls for writes from other machines everylogPollIntervalMs(250 ms by default), because the REST API keeps no blocking reads. Treat a call as “something changed” andreadto catch up.
The store passes all of TanStack AI’s log, ownership, input-lane, snapshot, and cross-instance
conformance cases. upstashLogStore(redis, prefix, { pollIntervalMs }) is also exported from
@upstash/agentkit-tanstack-ai/persistence for standalone use.
How to store provider API keys and OAuth tokens#
upstashCredentials() is a CredentialStore (from @tanstack/ai/byok) for API keys and OAuth tokens that
users or organizations saved. TanStack AI’s storedProviderKeys() reads from it.
Each owner (tenantId plus userId) has its own hash, so list returns ids and types only and never a
secret. getVersioned and setIf are compare-and-set on a revision that never comes back after a
delete, so several instances refreshing one OAuth token cannot overwrite each other.
Encryption at rest
Set encryptionKey to encrypt secrets with AES-256-GCM. It is 32 bytes: a Uint8Array, or a base64,
base64url, or hex string (openssl rand -base64 32). Without it, secrets are stored as plain JSON.
- The ciphertext is bound to the key
prefix, the owner, the credential id, and the type and expiry thatlistshows. Changingprefix, or copying rows to another prefix, makes every encrypted row undecryptable. - Credentials saved before you added a key are still read as plaintext. Anyone who can write to your
Redis could swap an encrypted credential for a plaintext one. After you re-save your credentials with
the key, set
requireEncryption: trueto refuse plaintext rows. - The revision is not part of the ciphertext, so a writer to your Redis could replay an older ciphertext of the same credential. Encryption protects the data from being read, not from a writer.
- The store does not expire
secret_answercredentials. Delete them after reading.
How to resume a TanStack AI stream after a reload#
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.
Options
How to use distributed locks with TanStack AI#
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.
Options
How to add long-term memory to TanStack AI#
Memory plugs into TanStack AI’s memoryMiddleware(), which comes from its memory package:
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.
Options
How to cache tools and rate limit with TanStack AI#
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:
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.