When we started building [AI Copilots](/ai-copilots) at Liveblocks, we weren’t
trying to invent a new protocol. We just needed a way to keep an AI agent in
sync with the UI, across tabs, devices, and even after a refresh in the middle
of a task.

Most AI agents default to HTTP streaming. And that made sense for ChatGPT and
first-generation chat UIs. But the more we leaned into UI-first copilots with
front-end tool calls, confirmation flows, realtime feedback, and resumable
streams, the more things started to break.

So we turned to a solution we had years of experience scaling: a stable and
persistent [WebSocket stack](/infrastructure) with authentication, automatic
reconnection, and reliable message delivery.

This post isn’t prescriptive, and WebSockets aren’t the right answer in every
case. But they made our lives much easier in ways we didn’t anticipate. Here’s
what we learned.

## WebSockets made multi-tab persistence and real-time updates simple

HTTP’s request–response model works for basic interactions, but it struggles
with long-running processes, page refreshes, or multiple tabs. Once a request
ends, the connection is gone. To bridge that gap, teams usually add
infrastructure like polling, pub-sub servers, or custom session logic. This
becomes especially painful with LLMs, where responses can stream for extended
periods and users may join or rejoin mid-process.

WebSockets provide a persistent, bidirectional connection instead. The server
can push updates at any time, and clients can subscribe to an in-progress task
and immediately receive the latest state. Additionally, updates broadcast to all
connected tabs, browsers, and devices, which keeps the state consistent without
extra coordination logic.

<Figure
  caption={
    <>
      Multiple streaming chats & live updates in our{" "}
      <UniversalLink href="/examples/ai-chats">AI Chats</UniversalLink> example.
    </>
  }
>
  <video autoPlay loop muted playsInline>
    <source
      src="/images/blog/ai-copilots-websockets/live-updates-multi-tabs.mp4"
      type="video/mp4"
    />
  </video>
</Figure>

At Liveblocks, our sync layer was already built on WebSockets for
[multiplayer editing](/multiplayer-editing), so Copilots inherited persistence
and multi-tab support without us adding new queues or background processes.

For teams starting fresh, the tradeoff is clear. You can patch around HTTP’s
limitations with additional infrastructure, but WebSockets remove that class of
problems entirely.

## Tool calls with human confirmation work better on WebSockets

Copilots need to [do more than return text](/ai-copilots). They should call
tools, render UI, and give users control when manual confirmation is required.
WebSockets are especially valuable here because every client stays in sync
whenever a user acts.

With HTTP, a confirmation is scoped to a single tab, meaning that if a user has
multiple tabs open or collaborators are working together, others will not see
that the action was already confirmed or denied. This can cause duplicate or
conflicting actions.

With WebSockets, the confirmation event is broadcast to all connected clients.
As soon as someone clicks “Confirm”, every session updates in real time and the
state stays consistent. An example of this is in our
[AI Dashboard](/examples/ai-dashboard-reports) demo, where the copilot can
suggest inviting a new member, but the action only runs after the human approves
it.

<Figure>
  <video autoPlay loop muted playsInline>
    <source
      src="/images/blog/liveblocks-3-0/invite-member.mp4"
      type="video/mp4"
    />
  </video>
</Figure>

```jsx
<AiTool name="invite-member">
  <AiTool.Confirmation
    confirm={async () => {
      await sendInvite(args.email);
      return { data: { invitedEmail: args.email } };
    }}
    cancel={() => {
      respond({ cancel: true });
    }}
  >
    Invite {args.email} to the team?
  </AiTool.Confirmation>
</AiTool>
```

The AI proposes the action, the human confirms, and the decision is streamed via
WebSockets so all tabs and collaborators immediately see the same outcome.

## Why teams choose HTTP, WebSockets, or both

There isn’t a single "right" way to connect AI to the client. Teams pick
different approaches based on tradeoffs. HTTP streaming is straightforward and
stateless, which makes it a natural fit for simple request–response interactions
like text completions or image generation. WebSockets introduce persistent,
bidirectional channels that are better for real-time feedback, multi-user sync,
and long-running tasks. Many modern products blend the two.

Based on our research, here’s how some well-known products have approached the
problem:

<Table scrollable>
  <TableHead>
    <TableRow>
      <TableCell header width={300} align="left">
        WebSocket-first
      </TableCell>
      <TableCell header width={300} align="left">
        HTTP-first
      </TableCell>
      <TableCell header width={300} align="left">
        Hybrid
      </TableCell>
    </TableRow>
  </TableHead>
  <TableBody>
    <TableRow>
      <TableCell width={300} align="left">
        **[Figma AI](https://www.figma.com/ai)**: Multiplayer editing with
        real-time AI suggestions
      </TableCell>
      <TableCell width={300} align="left">
        **[ChatGPT API](https://platform.openai.com/docs/api-reference)**:
        Request/response completions streamed over HTTPS/SSE
      </TableCell>
      <TableCell width={300} align="left">
        **[Vercel v0](https://v0.dev)**: HTTP streaming pipeline + WebSockets
        for updates
      </TableCell>
    </TableRow>
    <TableRow>
      <TableCell width={300} align="left">
        **[Notion AI](https://www.notion.so/product/ai)**: Shared context across
        editors and copilots
      </TableCell>
      <TableCell width={300} align="left">
        **[Midjourney](https://www.midjourney.com/) /
        [DALL·E](https://openai.com/dall-e-3)**: Image generation as one-off
        jobs
      </TableCell>
      <TableCell width={300} align="left">
        **[GitHub Copilot](https://github.com/features/copilot)**: HTTP for
        completions, sockets inside the IDE for streaming
      </TableCell>
    </TableRow>
    <TableRow>
      <TableCell width={300} align="left">
        **[Runway](https://runwayml.com/)**: AI video editing synced across
        users
      </TableCell>
      <TableCell width={300} align="left">
        **[Anthropic Claude
        API](https://docs.anthropic.com/claude/reference/getting-started-with-the-api)**:
        Stateless text interactions
      </TableCell>
      <TableCell width={300} align="left">
        **[Replit Ghostwriter](https://replit.com/ghostwriter)**: HTTP for
        background analysis, WebSockets for in-editor suggestions
      </TableCell>
    </TableRow>
    <TableRow>
      <TableCell width={300} align="left">
        **[Devin AI](https://devin.ai/)**: AI software engineer with continuous
        tasks, realtime collaboration, and persistent context
      </TableCell>
      <TableCell width={300} align="left">
        **[Zapier AI Actions](https://actions.zapier.com/)**: Workflow triggers
        as HTTP calls
      </TableCell>
      <TableCell width={300} align="left">
        **Some ChatGPT multiplayer wrappers**: Add sockets for shared sessions
      </TableCell>
    </TableRow>
  </TableBody>
</Table>

You can also see where HTTP-only setups start to break down: long-running agent
tasks fail on refresh, multi-tab sessions collide, and developers bolt on
polling or pub-sub to compensate. WebSockets handle these cases natively.

For us, leaning on the WebSocket stack we had for
[multiplayer editing](/multiplayer-editing) meant Copilots inherited
persistence, real-time delivery, and shared state without extra infrastructure.

## Final thoughts

WebSockets are not always the right choice. But for copilots that need
persistence, cross-tab consistency, and human-in-the-loop flows, they solved
problems that HTTP would have forced us to patch with extra infrastructure.

If you're building similar experiences, a persistent connection may save you
more time than you expect.

---

## FAQs

<FaqSection />