Install in Claude Code
Copygit clone --depth 1 https://github.com/TanStack/ai /tmp/ai-persistence && cp -r /tmp/ai-persistence/packages/ai-persistence/skills/ai-persistence ~/.claude/skills/ai-persistenceThen start a new Claude Code session; the skill loads automatically.
Definition
SKILL.md
# TanStack AI Persistence
> Builds on the `ai-core` skill in `@tanstack/ai`, and usually
> `ai-core/chat-experience`.
TanStack AI splits **delivery durability** from **state persistence**. They
share no code and solve different problems.
| Layer | Answers | Package / API |
| ----------------------- | ----------------------------------- | -------------------------------------------------------------------------------------------- |
| **Delivery durability** | Reconnect to a stream still running | `memoryStream` / `@tanstack/ai-durable-stream` on the response; see resumable streams docs |
| **State persistence** | What is the conversation, later? | Client `persistence` on `useChat` + server `withPersistence` from `@tanstack/ai-persistence` |
A replayable stream is **not** a saved conversation. A saved conversation is
**not** a live stream. Production apps often use both.
## Persistence is a contract, not a database
`@tanstack/ai-persistence` ships the **store interfaces**, the middleware that
drives them, an in-memory reference backend, and a conformance testkit. It does
**not** ship a backend for your database, and you do not need one: implement the
stores against whatever you already run — Postgres, SQLite, D1, Mongo — and hand
the result to `withPersistence`. The core never inspects your tables.
| Ships in the package | What it is |
| --------------------------------------------------------------------------- | ----------------------------------------------------------- |
| `MessageStore` / `RunStore` / `InterruptStore` / `MetadataStore` | The four **chat** state contracts |
| `GenerationRunStore` / `ArtifactStore` / `BlobStore` | The **generation** contracts (job lifecycle + bytes) |
| `withPersistence` / `withGenerationPersistence` | Chat + generation middleware |
| `memoryPersistence()` | In-process reference backend, all seven stores (dev, tests) |
| `reconstructChat` / `reconstructGeneration` | Server hydrate route helpers (chat / generation) |
| `retrieveArtifact` / `retrieveBlob` / `resolveArtifactBlobKey` | Serve persisted generation-media bytes back |
| `LockStore` / `withLocks` / `InMemoryLockStore` (from `@tanstack/ai/locks`) | Coordination, **not** this package — see ai-core/locks |
| `@tanstack/ai-persistence/testkit` | `runPersistenceConformance` gate (chat state stores) |
**Chat vs generation stores.** Chat persistence keys on `threadId` and uses
`messages` + optional `runs` / `interrupts` / `metadata`. Generation persistence
keys on its own `runId` and uses `generationRuns` (required by `withGenerationPersistence`) plus an
optional `artifacts` + `blobs` **pair** — provide both or neither — to store the
generated media bytes at blob key `artifacts/<runId>/<artifactId>`. A generation
run's identity is its own `runId`, but `threadId` is **required** on the record:
it is the stable slot successive runs fill, and `findLatestForThread` — the only
query that hydrates a run — keys on it. To
build the R2/D1-backed byte stores for a Worker, see
**ai-persistence/build-cloudflare-artifact-store**.
**Where bytes land.** Default blob key is `artifacts/<runId>/<artifactId>`. Pass
`storageKey` to `withGenerationPersistence` for your own folder structure — it
receives `{ artifactId, runId, threadId, role, activity, path, mimeType, name }`
and returns the key. Server-side only (a browser-supplied key is path traversal +
cross-tenant writes). The resolved key is recorded on `ArtifactRecord.blobKey`
because it is no longer derivable; read through `resolveArtifactBlobKey(record)`,
never by recomputing. Records predating `blobKey` fall back to the default
convention — which is why that convention can never be changed retroactively. A
non-unique key overwrites, so include `artifactId` unless that is intended.
**Byte storage stores generated output, not prompt URLs.** Provider result URLs
expire, so they are downloaded and kept. Prompt media sent as base64
(`source: { type: 'data' }`) is stored too. Prompt media sent as a **URL** is
NOT fetched — that URL is caller-supplied, so downloading it server-side is an
SSRF vector, and the bytes are redundant. Apps that genuinely need a durable
copy opt in with `allowInputUrl`, a predicate so the check can't be skipped:
`allowInputUrl: ({ url }) => url.hostname.endsWith('.cdn.example.com')`. Never
suggest `() => true`. All artifact fetches are http/https-only, timed out
(`artifactFetchTimeoutMs`) and size-capped (`maxArtifactBytes`); input fetches
also block loopback/private/link-local hosts and refuse redirects. `artifactFetch`
injects the `fetch`, for routing through an egress-restricted proxy.
Two related route-level rules: a `GET` that serves artifact bytes by id MUST
authorize the caller against `ArtifactRecord.threadId` before serving (404, not
403, so valid ids aren't confirmed), and `reconstructGeneration` MUST be given
`authorize` on any multi-user route. Both take ids straight from the caller.
Portable sandbox snapshots use the same `messages`, `artifacts`, and `blobs`
stores. Their artifact reader checks the checkpoint thread, but it does not
authenticate a caller. Authorize the thread before any route reads a snapshot
artifact. The snapshot checkpoint store also needs atomic append and fork
operations. A SQLite adapter must write a checkpoint, its head, and blob
reference counts in one transaction.
## Sub-skills
| Need to... | ReadMore from this repository
gap-analysisSkill
>
triage-githubSkill
Triage all open GitHub issues, PRs, and discussions in the current repository by fanning out up to 100 parallel subagents (one per item), then produce a single prioritized report ranking which PRs to review first, which issues to address first, and which discussions need maintainer attention. Use when the user asks to "triage open issues/PRs", "triage discussions", "prioritize the backlog", "what should I review first", "sweep the repo", or any request to bulk-evaluate open GitHub work and recommend an order.
ai-code-modeSkill
>
ai-mcpSkill
>
ai-coreSkill
>
ai-core/adapter-configurationSkill
>
ai-core/ag-ui-protocolSkill
>
ai-core/chat-experienceSkill
>