# Local runtime and custom backends
URL: /docs/cloud/local-runtime

Give any backend cloud threads, persistence, titles and run reports through useLocalRuntime, or compose the cloud thread list yourself.

> For AI agents: a documentation index is available at [llms.txt](/llms.txt). Use `.md` for canonical markdown pages; `.mdx` is kept as a backwards-compatible alias on supported URL paths.

`useLocalRuntime` runs your own chat model adapter, whatever protocol your backend speaks. Given a `cloud`, it gains the full cloud integration: the thread list, message persistence in the `aui/v0` format, automatic titles, run reports, engagement events, feedback and attachments.

## Setup

```
"use client";

import { useMemo } from "react";
import {
  AssistantCloud,
  AssistantRuntimeProvider,
  useLocalRuntime,
  type ChatModelAdapter,
} from "@assistant-ui/react";
import { ThreadList } from "@/components/assistant-ui/elements/thread-list.aui";
import { Thread } from "@/components/assistant-ui/elements/thread.aui";

const adapter: ChatModelAdapter = {
  async *run({ messages, abortSignal }) {
    const response = await fetch("/api/chat", {
      method: "POST",
      body: JSON.stringify({ messages }),
      signal: abortSignal,
    });
    const reader = response.body!.getReader();
    const decoder = new TextDecoder();
    let text = "";
    for (;;) {
      const { value, done } = await reader.read();
      if (done) break;
      text += decoder.decode(value, { stream: true });
      yield { content: [{ type: "text", text }] };
    }
  },
};

async function getToken() {
  // Return a user token from your auth provider.
  return "...";
}

export default function ChatPage() {
  const cloud = useMemo(
    () =>
      new AssistantCloud({
        baseUrl: process.env.NEXT_PUBLIC_ASSISTANT_BASE_URL!,
        authToken: getToken,
      }),
    [],
  );
  const runtime = useLocalRuntime(adapter, { cloud });

  return (
    <AssistantRuntimeProvider runtime={runtime}>
      <div className="grid h-dvh grid-cols-[240px_1fr]">
        <ThreadList />
        <Thread />
      </div>
    </AssistantRuntimeProvider>
  );
}
```

On React, `useLocalRuntime(adapter)` with `NEXT_PUBLIC_ASSISTANT_BASE_URL` set and no `cloud` uses an anonymous client, the same as the AI SDK runtime. On React Native and Ink construct the client from `assistant-cloud` and pass it.

`useDataStreamRuntime` from `@assistant-ui/react-data-stream`, the runtime for a route that returns an assistant-stream response, accepts every local runtime option including `cloud`, and gets the same integration.

## What the runtime does with the cloud

- **Persistence.** Each message is stored as the assistant-ui `ThreadMessage`, format `aui/v0`, with its parts, status and metadata, and reloaded on the next visit. A run paused for tool approval is stored when it pauses and updated when it resumes, and is not reported until it finishes.
- **Reports.** A run's status and outcome come straight from the message status the runtime keeps: a stop reports `aborted`, a model length limit `length`, a failure the error and its code. The runtime measures duration, time to first token and step timing itself. Model, provider, usage and trace id are read from the message metadata, which your adapter fills:

```
yield {
  content: [{ type: "text", text }],
  metadata: {
    custom: { modelId: "gpt-5.6-luna", provider: "openai", traceId },
    unstable_state: null,
  },
};
```

`custom.modelId`, `custom.provider` and `custom.traceId` are the keys the report reads on this format; per step usage rides on `metadata.steps[].usage`. See [Run reports](/docs/cloud/telemetry).

- **Everything else** is the same as the AI SDK runtime: titles after the first response, engagement events from the store, feedback and attachments through the default adapters.

## Composing the thread list yourself

The AI SDK, LangGraph and local runtimes all build on one hook, which you can use directly when you write a runtime of your own:

```
import { useCloudThreadListRuntime } from "@assistant-ui/react";

const runtime = useCloudThreadListRuntime({
  cloud,
  runtimeHook: useMyThreadRuntime,
  create: async () => ({ externalId: await backend.createThread() }),
  delete: async (threadId) => backend.deleteThread(threadId),
});
```

| Option        | Meaning                                                                                                             |
| ------------- | ------------------------------------------------------------------------------------------------------------------- |
| `cloud`       | The client the list is backed by.                                                                                   |
| `runtimeHook` | A hook returning the per thread runtime; it is called once per mounted thread.                                      |
| `create`      | Called when a new thread is created; return your backend's id and it is stored as the cloud thread's `external_id`. |
| `delete`      | Called with the thread before the cloud thread is deleted.                                                          |

This is also the way to a runtime that has no `cloud` option of its own. `useAgUiRuntime` from `@assistant-ui/react-ag-ui` and `useA2ARuntime` from `@assistant-ui/react-a2a` read the history and attachments adapters from the context this hook provides, and `useExternalStoreRuntime` underneath them merges the feedback adapter from the same context, so passing either as the `runtimeHook` gives an AG-UI or A2A app the cloud thread list and titles, persistence of every message, feedback, attachments, engagement events, and run reports read from the stored assistant messages:

```
import { useCloudThreadListRuntime } from "@assistant-ui/react";
import { useAgUiRuntime } from "@assistant-ui/react-ag-ui";

const runtime = useCloudThreadListRuntime({
  cloud,
  runtimeHook: () => useAgUiRuntime({ agent }),
});
```

The lower level `useCloudThreadListAdapter({ cloud, create, delete, sdk })` returns the adapter alone, and `createCloudThreadListAdapter` builds it outside React. Both register the calling package's identity on the client through `sdk`, so the project's Settings › Telemetry lists your integration next to the SDK version.

## Custom history adapters

A runtime that wants to store messages in another shape implements `ThreadHistoryAdapter.withFormat(adapter)` with a `MessageFormatAdapter`, the same seam the AI SDK runtime uses for `ai-sdk/v6`. `createFormattedPersistence` in `assistant-cloud` handles the id mapping and parent chaining, and the adapter that `withFormat` returns exposes `reportTelemetry(items, { durationMs, stepTimestamps, message })`, which sends the run report; the `message` is the thread message the items came from, whose status and timing complete the report. See the [package reference](/docs/api-reference/integrations/assistant-cloud).