Four ways to wire assistant-ui to a backend without a framework adapter.
If no framework adapter fits your backend, this section gives you four building blocks. All four are built on one of the two core runtimes; understanding the layering (see architecture) makes the choice clear.
The four paths
| Path | Layered on | Wire shape | Choose when |
|---|---|---|---|
LocalRuntime | Core | You write a ChatModelAdapter.run function | The simplest case: you have a fetch call to make, want assistant-ui to handle state |
ExternalStoreRuntime | Core | You provide messages + callbacks | You already have state in redux, zustand, tanstack-query, or anywhere |
DataStream | LocalRuntime + protocol | Your backend emits the data stream protocol | You want a thin message-stream contract, or you are migrating from AI SDK v4 |
AssistantTransport | ExternalStoreRuntime + protocol | Your backend streams agent state snapshots | Your agent has rich internal state and you want it surfaced in the UI; or you need bidirectional commands |
Decision tree
do you have an AI SDK / LangGraph / Google ADK / A2A backend?
└── yes → use that framework adapter; you do not need this section
└── no → continue
│
do you already have message state in redux / zustand / tanstack-query?
└── yes → ExternalStoreRuntime
└── no → continue
│
do you control the backend wire format?
└── yes, and i want simple fetch calls → LocalRuntime
└── yes, and i want to stream a structured agent state → AssistantTransport
└── no, the backend already speaks data stream protocol → DataStreamWhat each path gives you
LocalRuntime is the lowest-friction path. You implement one method (run or async *run). Branching, editing, regeneration, multi-thread, and adapter slots all work without extra code.
ExternalStoreRuntime is the inverse: you own the message array and provide callbacks for each interaction. UI features turn on based on which callbacks you provide. Ideal when chat state is a first-class entity in your existing store.
DataStream is LocalRuntime plus a wire protocol. You do not write a ChatModelAdapter; your backend emits a standardized stream of message parts and the runtime consumes it directly.
AssistantTransport is ExternalStoreRuntime plus a state-streaming protocol. Instead of sending message parts, your backend sends snapshots of its full agent state. The UI is a stateless view on top of that state. Supports custom commands beyond the built-in add-message and add-tool-result.
Common building blocks
Regardless of which path you pick, the adapter and thread interfaces work the same way. Wire attachments, history, speech, feedback, suggestions, and multi-thread support through those shared contracts.