cloud · assistant-ui — Claude Code skill: AssistantCloud persistence for assistant-ui
Wires AssistantCloud into an assistant-ui app for cross-session thread history, message feedback and file uploads.
npx skills add assistant-ui/skills --skill cloud -a claude-codeWhat cloud does
The skill covers hooking the assistant-ui runtime up to AssistantCloud, a hosted service that stores threads and messages, titles new threads and records user feedback. A single AssistantCloud client serves useChatRuntime from @assistant-ui/ai-sdk, AISDKThreads({ cloud }) for AuiConfig hosts, and useLangGraphRuntime from @assistant-ui/react-langgraph.
Three auth modes are documented: authToken with a JWT, apiKey plus userId and workspaceId for server-side use only, and anonymous for demos. There are separate references for Clerk, Auth0, Supabase and Firebase, plus a backend token endpoint. The client surface is cloud.threads, cloud.files, cloud.runs and cloud.telemetry, and a custom ThreadHistoryAdapter can sit on top of CloudMessagePersistence.
Install the packages first: npm install @assistant-ui/react @assistant-ui/ai-sdk. assistant-cloud comes in as a dependency of @assistant-ui/react and re-exports AssistantCloud for browser code, while server-side code imports it from assistant-cloud directly. anonymous is fine for a demo, but production needs a token. Threads that never persist, 401s from the cloud API and feedback buttons that save nothing are the cases this skill is meant for.
What it helps with
- keep thread history across sessions
- add auth through Clerk, Auth0, Supabase or Firebase
- let users upload files into the chat
- store message feedback from the UI
- debug 401s from the cloud API and feedback that does not save
Example prompts for the agent
- Wire AssistantCloud into useChatRuntime so threads persist across sessions
- Set up Clerk auth with a backend token endpoint
- Add file uploads through cloud.files to my assistant-ui chat
How to install cloud
1Skills CLI (Vercel)
npx skills add assistant-ui/skills --skill cloud -a claude-code2Manually: put the files in place
~/.claude/skills/cloud/SKILL.mdCopy the whole skill folder; for one project use .claude/skills/cloud/
1Skills CLI (Vercel)
npx skills add assistant-ui/skills --skill cloud -a codex2Manually: put the files in place
.agents/skills/cloud/SKILL.mdFor all projects use ~/.agents/skills/
1Skills CLI (Vercel)
npx skills add assistant-ui/skills --skill cloud -a cursor2Manually: put the files in place
.cursor/skills/cloud/SKILL.mdCursor also reads .agents/skills/ and .claude/skills/
1GitHub CLI
gh skill install assistant-ui/skills cloud2Manually: put the files in place
.github/skills/cloud/SKILL.md1Skills CLI (Vercel)
npx skills add assistant-ui/skills --skill cloud -a gemini-cli2Platform CLI
gemini skills install https://github.com/assistant-ui/skills --path assistant-ui/skills/cloudManually: put the files in place
.devin/skills/cloud/SKILL.mdThe legacy .windsurf/skills/ path still works
Skills CLI (Vercel)
npx skills add assistant-ui/skills --skill cloud -a opencodeClaude.ai
Zip the folder and upload it in Settings → Capabilities → Skills
Step by step, with commands for each platform: How to install skills in Claude Code: five ways
Questions about cloud
How do I install cloud in Claude?
Run npx skills add assistant-ui/skills --skill cloud -a claude-code. The skill lands in ~/.claude/skills/cloud/SKILL.md.
What does the cloud skill do?
It shows how to connect AssistantCloud to assistant-ui: thread and message persistence, authorization, telemetry, file uploads and message feedback. It covers useChatRuntime, AISDKThreads({ cloud }) and useLangGraphRuntime.
Which packages do I need?
npm install @assistant-ui/react @assistant-ui/ai-sdk. AssistantCloud is re-exported from @assistant-ui/react for browser code, and server-side code imports it from assistant-cloud.
Source text
SKILL.md · MIT
---
name: cloud
description: "Adds AssistantCloud backed persistence, authorization, and telemetry to assistant-ui apps. Use when wiring cross-session thread and message history, multi-device chat, message feedback, file uploads, or auth: passing `cloud` to `useChatRuntime` from `@assistant-ui/ai-sdk`, `AISDKThreads({ cloud })` for `AuiConfig` hosts, or `cloud` on `useLangGraphRuntime`. Covers constructing `AssistantCloud` with `authToken` (JWT), `apiKey` plus `userId`/`workspaceId` (server-side only), or `anonymous`; Clerk, Auth0, Supabase, and Firebase integrations and a backend token endpoint; the `cloud.threads`, `cloud.files`, `cloud.runs`, and `cloud.telemetry` client surface; and a custom `ThreadHistoryAdapter` on `CloudMessagePersistence`. Route here for threads that do not persist, 401s against the cloud API, or feedback buttons that do not save. For the `<ThreadList />` sidebar UI use thread-list; for the general adapter contracts use runtime."
license: MIT
---
# assistant-ui Cloud
**Always consult [assistant-ui.com/llms.txt](https://www.assistant-ui.com/llms.txt) for the latest API.**
Assistant Cloud is a hosted service that adds thread persistence, message history, auto-generated titles, message feedback, and file uploads to any React chat UI. One `AssistantCloud` client backs the assistant-ui runtime, `AuiConfig` thread integration, and LangGraph Cloud.
## Contents
- [References](#references) | [Install](#install) | [Quick start: useChatRuntime](#quick-start-usechatruntime) | [AuiConfig hosts: AISDKThreads](#auiconfig-hosts-aisdkthreads) | [Migrate from Cloud AI SDK](#migrate-from-cloud-ai-sdk) | [LangGraph](#langgraph-uselanggraphruntime) | [Message feedback](#message-feedback) | [Authentication](#authentication) | [Client API](#client-api) | [Environment variables](#environment-variables) | [Common Gotchas](#common-gotchas) | [Related Skills](#related-skills)
## References
- [./references/persistence.md](./references/persistence.md) -- the thread, message, file, and run client API in full, verified against source
- [./references/authorization.md](./references/authorization.md) -- the three auth modes and direct provider integrations (Clerk, Auth0, Supabase, Firebase)
- [./references/custom-persistence.md](./references/custom-persistence.md) -- `CloudMessagePersistence` and `createFormattedPersistence`, for a custom `ThreadHistoryAdapter` backed by cloud message storage
- [./references/auth-integrations.md](./references/auth-integrations.md) -- non-cloud auth gating with Auth.js (next-auth), Clerk, and better-auth, plus pairing AssistantCloud with a backend token endpoint
## Install
```bash
npm install @assistant-ui/react @assistant-ui/ai-sdk
```
`assistant-cloud` ships as a dependency of `@assistant-ui/react`, which re-exports `AssistantCloud` for browser code. Server-side code (API routes, token endpoints) that has no reason to depend on `@assistant-ui/react` imports `AssistantCloud` from `assistant-cloud` directly.
## Quick start: useChatRuntime
`useChatRuntime({ cloud })` from `@assistant-ui/ai-sdk` wraps AI SDK's `useChat` and adds persistence. It creates a cloud thread on the first message, persists messages as they stream, generates a title after the first response, loads history on thread switch through `<ThreadList />`, and submits message feedback against the stored message.
```tsx
"use client";
import { useMemo } from "react";
import { AssistantCloud, AssistantRuntimeProvider } from "@assistant-ui/react";
import { useChatRuntime } from "@assistant-ui/ai-sdk";
import { Thread } from "@/components/assistant-ui/elements/thread.aui";
import { ThreadList } from "@/components/assistant-ui/elements/thread-list.aui";
export default function ChatPage() {
const cloud = useMemo(
() =>
new AssistantCloud({
baseUrl: process.env.NEXT_PUBLIC_ASSISTANT_BASE_URL!,
anonymous: true,
}),
[],
);
const runtime = useChatRuntime({ cloud });
return (
<AssistantRuntimeProvider runtime={runtime}>
<div className="grid h-dvh grid-cols-[250px_1fr] gap-x-2">
<ThreadList />
<Thread />
</div>
</AssistantRuntimeProvider>
);
}
```
`anonymous: true` is for demos; see [Authentication](#authentication) for production auth. `useChatRuntime` also accepts `adapters.history` (a `ThreadHistoryAdapter`, must implement `withFormat` to combine with AI SDK), `adapters.feedback` (defaults to the cloud feedback adapter when `cloud` is set), `adapters.attachments`, `toCreateMessage`, `transport` (defaults to `AssistantChatTransport` calling `/api/chat`), and `onResumeError`.
## AuiConfig hosts: AISDKThreads
`AISDKThreads({ cloud })` from `@assistant-ui/ai-sdk` is the `AuiConfig` equivalent for a host that is not `AssistantRuntimeProvider` (Vue, or a hand-rolled `AssistantClient`). With `cloud` set, the thread list is a `RemoteThreadList` with background threads: every visited thread keeps running and its own history after a switch, and a freshly created thread titles itself.
```tsx
import { AuiConfig, AuiProvider } from "@assistant-ui/react";
import { AISDKThreads } from "@assistant-ui/ai-sdk";
const config = AuiConfig({
threads: AISDKThreads({ cloud }),
});
```
## Migrate from Cloud AI SDK
`@assistant-ui/cloud-ai-sdk` is deprecated. For new code, use the `cloud` option of `useChatRuntime` from `@assistant-ui/ai-sdk`; for an `AuiConfig` host, use `AISDKThreads({ cloud })`. See [Migrate from Cloud AI SDK](https://www.assistant-ui.com/docs/cloud/migrate-cloud-ai-sdk) for the migration details.
## LangGraph: useLangGraphRuntime
`useLangGraphRuntime` from `@assistant-ui/react-langgraph` takes `cloud` alongside `stream`, `create`, `load`, and `delete` for cloud-backed thread management over a LangGraph Cloud backend.
```tsx
"use client";
import { AssistantCloud, AssistantRuntimeProvider } from "@assistant-ui/react";
import { useLangGraphRuntime, type LangChainMessage } from "@assistant-ui/react-langgraph";
import { useMemo } from "react";
import { createThread, deleteThread, getThreadState, sendMessage } from "@/lib/chatApi";
export function MyRuntimeProvider({ children }: { children: React.ReactNode }) {
const cloud = useMemo(
() => new AssistantCloud({ baseUrl: process.env.NEXT_PUBLIC_ASSISTANT_BASE_URL!, anonymous: true }),
[],
);
const runtime = useLangGraphRuntime({
cloud,
stream: async function* (messages, { initialize }) {
const { externalId } = await initialize();
if (!externalId) throw new Error("Thread not found");
return sendMessage({ threadId: externalId, messages });
},
create: async () => ({ externalId: (await createThread()).thread_id }),
load: async (externalId) => {
const state = await getThreadState(externalId);
return { messages: (state.values as { messages?: LangChainMessage[] }).messages ?? [] };
},
delete: async (externalId) => deleteThread(externalId),
});
return <AssistantRuntimeProvider runtime={runtime}>{children}</AssistantRuntimeProvider>;
}
```
`create` returns `{ externalId }`, your backend's LangGraph thread id; `load` returns that thread's messages (and optionally interrupts); `delete` receives the `externalId` and, when provided, enables delete from the thread list UI.
## Message feedback
When `cloud` is set on `useChatRuntime` (or `AISDKThreads`), the runtime wires a default `FeedbackAdapter` that calls `cloud.threads.messages.feedback` automatically, so the built-in thumbs up and down buttons persist against the stored message with no extra code. Calling it directly:
```ts
const { feedback_id, type } = await cloud.threads.messages.feedback(threadId, messageId, {
type: "positive", // or "negative"
});
```
## Authentication
Three constructor shapes, chosen by which fields you pass:
```ts
// JWT (recommended for production; client-side)
const cloud = new AssistantCloud({
baseUrl: process.env.NEXT_PUBLIC_ASSISTANT_BASE_URL!,
authToken: async () => getAuthToken(), // returns a JWT, or null
});
// API key (server-side only; baseUrl defaults to https://backend.assistant-api.com)
const cloud = new AssistantCloud({
apiKey: process.env.ASSISTANT_API_KEY!,
userId: user.id,
workspaceId: user.workspaceId,
});
// Anonymous (demos and prototypes; a new user per browser session)
const cloud = new AssistantCloud({
baseUrl: process.env.NEXT_PUBLIC_ASSISTANT_BASE_URL!,
anonymous: true,
});
```
Authorization is scoped to a workspace (commonly `userId`, `orgId_userId`, or `projectId_userId`). Wire a direct provider integration (Clerk, Auth0, Supabase, Firebase) from the Assistant Cloud dashboard, or mint a token server-side with the API key client and return it from an endpoint your frontend's `authToken` callback fetches. See [authorization.md](./references/authorization.md) for both paths in full, and [auth-integrations.md](./references/auth-integrations.md) for pairing Cloud with Auth.js, Clerk, or better-auth session data.
## Client API
Every method is verified against `src/cloud/**`. `threadId`/`messageId` are always the first arguments; list endpoints page with `after` (a cursor, not an offset).
```ts
// Threads
const { threads } = await cloud.threads.list({ is_archived: false, limit: 50, after: cursor });
const thread = await cloud.threads.get(threadId);
const { thread_id } = await cloud.threads.create({ last_message_at: new Date(), title, external_id, metadata });
await cloud.threads.update(threadId, { title, last_message_at, metadata, is_archived });
await cloud.threads.delete(threadId);
// Messages (parent_id and format are required on create; update touches content only)
const { messages } = await cloud.threads.messages.list(threadId, { format: "ai-sdk/v6" });
const { message_id } = await cloud.threads.messages.create(threadId, { parent_id, format, content });
await cloud.threads.messages.update(threadId, messageId, { content });
await cloud.threads.messages.feedback(threadId, messageId, { type: "positive" });
// Files
const { signedUrl, publicUrl, expiresAt } = await cloud.files.generatePresignedUploadUrl({ filename });
await fetch(signedUrl, { method: "PUT", body: file });
// Runs, projects, auth
await cloud.runs.report(runReport); // telemetry sink; the runtime calls this for you
const { threads: projectThreads } = await cloud.projects.threads.list({ limit: 50, after: cursor }); // whole project, not one workspace
const { messages: projectMessages } = await cloud.projects.threads.messages.list(threadId, { limit: 50, after: cursor });
const { token } = await cloud.auth.tokens.create(); // server-side, API key mode
cloud.telemetry; // { enabled: boolean, beforeReport? }
```
`cloud.threads.create`'s `last_message_at` is required; everything else on create and update is optional. `CloudThread` fields: `id`, `title`, `last_message_at`, `created_at`, `updated_at`, `is_archived`, `external_id`, `metadata`, `project_id`, `workspace_id`. `CloudMessage` fields: `id`, `parent_id`, `height`, `format`, `content`, `created_at`, `updated_at`. `content`/`format` are opaque to the client; `useChatRuntime` and `AISDKThreads` write `format: "ai-sdk/v6"` with an AI SDK `UIMessage`-shaped `content`. `cloud.projects.threads.messages.list` accepts `limit`/`after` paging that `cloud.threads.messages.list` does not. Failed requests throw `CloudAPIError` (with `.status`) from `assistant-cloud`; a malformed response throws `CloudResponseError`. Full detail, auto-save behavior, telemetry fields, and error handling: [persistence.md](./references/persistence.md).
## Environment variables
```env
NEXT_PUBLIC_ASSISTANT_BASE_URL=https://proj-[YOUR-ID].assistant-api.com # client-side
ASSISTANT_API_KEY=your-api-key-here # server-side only, never expose to the client
```
React Native reads `EXPO_PUBLIC_ASSISTANT_BASE_URL`; React Ink reads `ASSISTANT_BASE_URL` (no `NEXT_PUBLIC_` prefix, since there is no bundler exposing it to a browser).
## Common Gotchas
**Threads not persisting**
- `cloud` must be passed to `useChatRuntime` or `AISDKThreads`; a thread with no messages yet is never created.
- Check that `authToken` isn't silently resolving to `null`: any real request then throws a plain `Error("Authorization failed")` before it reaches the network, distinct from a `CloudAPIError` 401.
**401 or "Authorization failed" against the cloud API**
- Verify `baseUrl` matches the project's Frontend API URL exactly (no trailing content after the host).
- For JWT mode, confirm `authToken` resolves before the first request; for a Clerk template, the aud claim in the JWT must be `"assistant-ui"`.
**Feedback buttons render but nothing persists**
- The default feedback adapter only wires up when `cloud` is passed to the runtime; a custom `adapters.feedback` overrides it entirely.
**Message history reappears out of order after a page reload**
- Do not hand-write `content` for a message row; it must be exactly what the active format's `encode` produced, or `decode` fails on reload. Seed test data through the runtime, not by inserting rows directly.
**Importing from `@assistant-ui/react-ai-sdk`**
- That package only re-exports `@assistant-ui/ai-sdk` for installs pinned to an older version. Import `useChatRuntime` and `AssistantChatTransport` from `@assistant-ui/ai-sdk` in new code.
**Anonymous mode loses history**
- Anonymous creates a new user per browser session (no cross-device sync); switch to JWT or API key auth once you need durability.
## Related Skills
- [thread-list](../thread-list/SKILL.md) -- the `<ThreadList />` sidebar UI, thread CRUD selectors, and the `RemoteThreadListAdapter` contract for a fully self-hosted (non-cloud) thread list
- [setup](../setup/SKILL.md) -- installing assistant-ui and picking a runtime hook, including the `cloud` and `cloud-clerk` CLI templates
- [runtime](../runtime/SKILL.md) -- the general adapter contracts (`ThreadHistoryAdapter`, `FeedbackAdapter`, `AttachmentAdapter`) that cloud fulfills
© assistant-ui · MIT · assistant-ui/skills
Similar skills
Everything for Frontend and UI →cloud by other authors comes first.