Sign in

AI activity feed

Show your users what AI agents are doing in your app, as it happens. With Liveblocks you can build a multiplayer activity feed that streams each agent’s events in realtime, updates entries live as work progresses, keeps a permanent history, and notifies users when an agent finishes or needs input.

AI activity feed

AI activity feed in the AI Slideshow example

Features

Get started

Choose the features you need. Each guide uses Next.js and can be combined with the others.

Implementation

This is an overview of how each feature can be implemented. Activity events are stored in Feeds, part of Sync. Each agent or run publishes messages to a feed, and each message holds JSON data whose schema you define. Use Presence for the agent’s live in-room status and Notifications to reach users who are away.

Realtime activity feed

Read a feed with useFeedMessages and render each event with your own components. Events are persistent and delivered in realtime, so users watching the feed see new activity instantly, and the full history is still there when they reload.

import { useFeedMessages } from "@liveblocks/react/suspense";
function ActivityFeed({ feedId }: { feedId: string }) { const { messages } = useFeedMessages(feedId);
return ( <ol> {messages.map((message) => ( <li key={message.id} data-kind={message.data.kind}> {message.data.label} </li> ))} </ol> );}

Feeds is headless, meaning it stores and syncs your event data, but you handle the rendering and design. Learn more under the Feeds overview.

Publishing activity

Your agent’s back end appends events with Liveblocks.createFeedMessage. Publish one event per meaningful step, such as a search performed, a file edited, or a tool called, and connected clients receive each one in realtime.

import { Liveblocks } from "@liveblocks/node";
const liveblocks = new Liveblocks({ secret: process.env.LIVEBLOCKS_SECRET_KEY!,});
await liveblocks.createFeedMessage({ roomId: "project-room", feedId: "agent-activity", data: { kind: "search", label: "Searched 12 documents for “pricing”", },});

Events can also be published from workflow tools such as the Liveblocks n8n integration, or from Python and any other language with the REST API.

Streaming status updates

Long-running steps shouldn’t sit frozen in the feed. Create an event when a step starts, then update the same message with Liveblocks.updateFeedMessage as the agent progresses. Every connected user sees the entry change live, from “thinking” through to “complete”.

await liveblocks.createFeedMessage({  roomId: "project-room",  feedId: "agent-activity",  id: "step-4",  data: { kind: "write", status: "running", label: "Updating the report…" },});
// Run the step// ...
await liveblocks.updateFeedMessage({ roomId: "project-room", feedId: "agent-activity", messageId: "step-4", data: { kind: "write", status: "complete", label: "Updated the report" }, updatedAt: Date.now(),});

The same pattern streams generated text token by token into one entry, as shown in the chat use case.

AI presence

The feed records what an agent has done, while Presence shows that it’s working right now. Publish the agent’s live state with Liveblocks.setPresence when a run starts, and remove it when the run ends.

await liveblocks.setPresence("project-room", {  userId: "ai-agent",  userInfo: { name: "AI agent", color: "#7c3aed" },  data: { status: "working" },  ttl: 60,});

The agent then appears in useOthers like any human, so existing AvatarStack components and online indicators show AI activity with no extra UI. Learn more under the agentic users use case.

Multiple agents and runs

Each room can contain many feeds, so give each agent, task, or run its own. Attach metadata when creating a feed with Liveblocks.createFeed, then list and filter runs in your UI with useFeeds.

import { useFeeds } from "@liveblocks/react/suspense";
function RunList() { const { feeds } = useFeeds({ metadata: { kind: "agent-run" } });
return ( <nav> {feeds.map((feed) => ( <a key={feed.feedId} href={`/runs/${feed.feedId}`}> {feed.metadata.title} </a> ))} </nav> );}

Learn more under filtering feeds.

Activity history

Every event is saved permanently, so users who reconnect can audit exactly what an agent did while they were away. Long histories load in pages, as useFeedMessages returns up to 50 events by default, with pagination controls for loading earlier activity.

import { useFeedMessages } from "@liveblocks/react/suspense";
function ActivityHistory({ feedId }: { feedId: string }) { const { messages, fetchMore, hasFetchedAll, isFetchingMore } = useFeedMessages(feedId, { limit: 20 });
return ( <> {!hasFetchedAll && ( <button disabled={isFetchingMore} onClick={fetchMore}> Load earlier activity </button> )} {messages.map((message) => ( <div key={message.id}>{message.data.label}</div> ))} </> );}

Learn more under paginating feed messages.

Notifications

Agents often finish work, or need a decision, while users are away. Trigger a custom notification with Liveblocks.triggerInboxNotification when a run completes, and render it in an in-app inbox or deliver it by email.

await liveblocks.triggerInboxNotification({  userId: "olivier@example.com",  kind: "$aiRunComplete",  subjectId: "run-42",  activityData: {    title: "Report generated",    summary: "The Q3 report is ready for review",  },});

Learn more under the inbox use case and our Notifications overview.

Permissions

Each activity feed is contained inside a room in your Liveblocks app, and Feeds has its own permission scopes. Users watching agent activity only need feeds:read, while only your back end publishes events. This can be set when modifying or creating a room, for example with Liveblocks.createRoom.

await liveblocks.createRoom(`my-room-id`, {  defaultAccesses: [    // No access by default  ],  groupsAccesses: {    // "viewers" group can watch agent activity    viewers: ["feeds:read"],  },  usersAccesses: {    // "olivier" has write access to everything    olivier: ["*:write"],  },});

Server-side calls with a secret key are not limited by a user’s permissions, so agents can always publish. More complex controls can be set too, learn more under Permissions.

Examples

Explore complete examples that combine the features described above.