# How to Use the Agent-Native Collab Module for Real-Time Features

> Learn to implement real-time features with the Agent-Native collab module. Leverage Yjs for multi-user editing, conflict resolution, and server persistence without custom WebSocket code.

- Repository: [Builder.io/agent-native](https://github.com/BuilderIO/agent-native)
- Tags: how-to-guide
- Published: 2026-06-28

---

**The Agent-Native collab module provides a full-stack, CRDT-based real-time collaboration layer using Yjs that enables multi-user editing through React hooks, server-side persistence, and automatic conflict resolution without writing custom WebSocket code.**

The `BuilderIO/agent-native` repository ships with a production-ready collaboration system built on Yjs CRDTs. The Agent-Native collab module handles everything from client-side state synchronization to server-side persistence in the `_collab_docs` SQL table, allowing developers to add real-time features to any JSON-serializable resource such as slides, videos, or plans.

## Architecture of the Agent-Native Collab Module

The collab module splits responsibilities across client and server layers, using Yjs for conflict-free replicated data type (CRDT) merging and optimistic concurrency control.

### Server-Side Collab Plugin

The server implementation resides in [`packages/core/src/server/collab-plugin.ts`](https://github.com/BuilderIO/agent-native/blob/main/packages/core/src/server/collab-plugin.ts). This Nitro plugin mounts REST endpoints under `/_agent-native/collab/:docId/:action` and handles authentication guards, persistence, and real-time broadcasting.

Key components include:

- **Route Guards**: Every request validates `event.context.session` before processing, returning 401 for unauthenticated users
- **CollabEmitter**: Broadcasts Yjs updates to connected polling clients via the ring-buffer implemented in [`packages/core/src/server/poll-events.ts`](https://github.com/BuilderIO/agent-native/blob/main/packages/core/src/server/poll-events.ts)
- **Body Size Limits**: Write payloads are capped at 2MB by default to protect the database from oversized updates

The plugin supports actions including `state`, `update`, `awareness`, `json`, `patch`, and `delete`, routing each to the appropriate handler in the storage layer.

### Storage and Y-Doc Management

Persistent storage is managed through [`packages/core/src/collab/storage.ts`](https://github.com/BuilderIO/agent-native/blob/main/packages/core/src/collab/storage.ts), which interacts with the `_collab_docs` table containing `doc_id` (primary key), `yjs_state` (base64-encoded BLOB), and `version` (INTEGER) columns.

The [`packages/core/src/collab/ydoc-manager.ts`](https://github.com/BuilderIO/agent-native/blob/main/packages/core/src/collab/ydoc-manager.ts) file handles document lifecycle operations:

- **Seeding**: `seedFromJson` injects initial JSON into a fresh Y.Doc when creating new collaborative resources
- **Merging**: Applies incoming patches atomically via `setCollabState`, ensuring idempotent writes
- **Versioning**: Increments the `version` column on every successful write, enabling optimistic concurrency checks on the client

### Client-Side Integration

The client layer in [`packages/core/src/client/collab/index.ts`](https://github.com/BuilderIO/agent-native/blob/main/packages/core/src/client/collab/index.ts) exposes React hooks that abstract the polling-based real-time connection:

- **`useCollab({docId, initialState})`**: Returns a `Y.Doc` instance that automatically subscribes to `GET /collab/:docId/state` and pushes local changes via `POST /collab/:docId/update`
- **`useCollabAwareness`**: Manages cursor positions and user selections through awareness endpoints (`GET /collab/:docId/users` and `POST /collab/:docId/awareness`)

The client automatically registers **Tiptap** collaboration extensions (`@tiptap/extension-collaboration` and `@tiptap/extension-collaboration-caret`) defined in [`packages/core/src/vite/client.ts`](https://github.com/BuilderIO/agent-native/blob/main/packages/core/src/vite/client.ts), binding ProseMirror editors to the shared Y.Doc.

## Implementing Real-Time Collaboration

### Setting Up a Collab-Enabled Hook

Following the production pattern in [`templates/videos/app/hooks/use-composition-collab.ts`](https://github.com/BuilderIO/agent-native/blob/main/templates/videos/app/hooks/use-composition-collab.ts), create a hook that seeds documents and handles two-way synchronization:

```typescript
import { useCollab, useCollabAwareness } from '@agent-native/core/client/collab';
import { hasCollabState, pushCollabUpdate } from '@agent-native/core/collab';

export function useCompositionCollab(compositionId: string | null) {
  const docId = compositionId ? `composition:${compositionId}` : null;

  const { ydoc, ready, error } = useCollab({
    docId,
    seed: async () => {
      if (docId && !(await hasCollabState(docId))) {
        const json = await fetchCompositionJson(compositionId!);
        await pushCollabUpdate(docId, { type: 'seed', json });
      }
      return null;
    },
  });

  const { users, setAwareness } = useCollabAwareness({ docId });

  const composition = useMemo(() => {
    if (!ready) return null;
    return ydoc?.getMap('composition').toJSON();
  }, [ready, ydoc]);

  const save = async (newData: any) => {
    if (!docId) return;
    await pushCollabUpdate(docId, { type: 'patch', json: newData });
  };

  return { composition, save, users, setAwareness, ready, error };
}

```

This pattern checks for existing collab state with `hasCollabState`, seeds new documents with initial JSON when needed, and provides a `save` helper that pushes structured updates via `pushCollabUpdate`.

### Syncing Server Actions with Collab State

Server actions must write through to the collab layer to maintain synchronization across all connected clients. Based on [`templates/videos/actions/update-composition.ts`](https://github.com/BuilderIO/agent-native/blob/main/templates/videos/actions/update-composition.ts):

```typescript
import { pushCollabUpdate } from '@agent-native/core/collab';
import { sql } from 'drizzle-orm';

export async function updateSlide({ slideId, changes }) {
  // Update the canonical database first
  await db.update('slides')
    .set({ slide_json: sql`json_set(slide_json, '$', ${JSON.stringify(changes)})` })
    .where(eq('slides.id', slideId));

  // Push the same change to the collab document
  const collabDocId = `slide:${slideId}`;
  await pushCollabUpdate(collabDocId, { type: 'patch', json: changes });
}

```

When deleting resources, call `deleteCollabState` (as demonstrated in [`templates/plan/actions/delete-visual-plan.ts`](https://github.com/BuilderIO/agent-native/blob/main/templates/plan/actions/delete-visual-plan.ts)) to clean up the corresponding `_collab_docs` row and prevent orphaned collaboration data.

## Document Identification and Routing

The collab module uses structured `docId` patterns to identify collaborative resources:

- **Plan blocks**: `plan:${planId}:${blockId}`
- **Compositions**: `composition:${id}`
- **Slides**: `slide:${slideId}`

The server extracts these IDs from the URL route `/_agent-native/collab/:docId/:action` defined in [`packages/core/src/server/collab-plugin.ts`](https://github.com/BuilderIO/agent-native/blob/main/packages/core/src/server/collab-plugin.ts). The storage layer in [`packages/core/src/collab/storage.ts`](https://github.com/BuilderIO/agent-native/blob/main/packages/core/src/collab/storage.ts) uses these IDs as primary keys in the `_collab_docs` table, with `getCollabState` retrieving current binary state and `setCollabState` performing atomic upserts.

## Summary

- The **Agent-Native collab module** in `BuilderIO/agent-native` provides a complete Yjs-based CRDT solution for real-time editing without WebSocket infrastructure
- **Server-side** implementation in [`packages/core/src/server/collab-plugin.ts`](https://github.com/BuilderIO/agent-native/blob/main/packages/core/src/server/collab-plugin.ts) exposes REST endpoints with session authentication and persists to the `_collab_docs` table via [`packages/core/src/collab/storage.ts`](https://github.com/BuilderIO/agent-native/blob/main/packages/core/src/collab/storage.ts)
- **Client hooks** `useCollab` and `useCollabAwareness` in [`packages/core/src/client/collab/index.ts`](https://github.com/BuilderIO/agent-native/blob/main/packages/core/src/client/collab/index.ts) handle polling-based synchronization and cursor awareness
- **Document patterns** follow `type:${id}` format, with helpers like `hasCollabState`, `pushCollabUpdate`, and `seedFromJson` managing the data flow between SQL and Yjs
- **Template examples** in [`templates/videos/app/hooks/use-composition-collab.ts`](https://github.com/BuilderIO/agent-native/blob/main/templates/videos/app/hooks/use-composition-collab.ts) demonstrate production-ready integration patterns for React applications

## Frequently Asked Questions

### How does the Agent-Native collab module handle conflicts between simultaneous editors?

The module uses **Yjs CRDTs** (Conflict-free Replicated Data Types) implemented in [`packages/core/src/collab/ydoc-manager.ts`](https://github.com/BuilderIO/agent-native/blob/main/packages/core/src/collab/ydoc-manager.ts) to automatically merge concurrent changes. When multiple users push updates via `pushCollabUpdate`, the Y-Doc manager applies patches to the shared Y.Doc instance and persists the merged state with an incremented version number, ensuring all clients converge to the same document state without manual conflict resolution.

### What database table stores the collaborative document state?

The **`_collab_docs`** table defined in [`packages/core/src/collab/storage.ts`](https://github.com/BuilderIO/agent-native/blob/main/packages/core/src/collab/storage.ts) stores three columns: `doc_id` (primary key), `yjs_state` (base64-encoded binary Yjs document), and `version` (integer for optimistic concurrency). The `setCollabState` function performs atomic upserts to this table, while `getCollabState` retrieves the current binary state for client synchronization.

### Do I need to manage WebSocket connections for real-time updates?

No. The Agent-Native collab module uses a **polling-based architecture** rather than WebSockets. The client hooks in [`packages/core/src/client/collab/index.ts`](https://github.com/BuilderIO/agent-native/blob/main/packages/core/src/client/collab/index.ts) poll the `GET /collab/:docId/state` endpoint, while the server broadcasts updates through the `CollabEmitter` ring-buffer in [`packages/core/src/server/poll-events.ts`](https://github.com/BuilderIO/agent-native/blob/main/packages/core/src/server/poll-events.ts). This approach eliminates WebSocket infrastructure while maintaining sub-second synchronization for collaborative editing.

### How do I initialize a new collaborative document for an existing resource?

Use the **`seedFromJson`** pattern demonstrated in [`templates/videos/app/hooks/use-composition-collab.ts`](https://github.com/BuilderIO/agent-native/blob/main/templates/videos/app/hooks/use-composition-collab.ts). First check if a collab document exists using `hasCollabState(docId)`. If not, fetch your resource's JSON data and call `pushCollabUpdate(docId, { type: 'seed', json: initialData })`. This creates the initial Yjs document in the `_collab_docs` table with version 1, making it available for real-time collaboration.