---
title: Building an Agentic Chatbot with NoLag
description: "Step-by-step tutorial: combine @nolag/chat for the user-facing interface with @nolag/agents for AI-powered responses. Full code walkthrough."
excerpt: "Step-by-step tutorial: combine @nolag/chat for the user-facing interface with @nolag/agents for AI-powered responses. Full code walkthrough with escalation and monitoring."
date: '2026-05-19'
readTime: 14 min read
category: AI Agents
---

This tutorial walks through building a customer support chatbot that combines two NoLag Blueprints: `@nolag/chat` for the user-facing interface and `@nolag/agents` for AI-powered response generation. The result is a production-ready chatbot with typing indicators, conversation history, sentiment-based escalation, and full observability.

## Architecture Overview

The system has three components:

1. **Chat frontend** - Users interact with a standard chat room powered by `@nolag/chat`. They see typing indicators, message history, and presence.
2. **Bridge service** - A server-side process that connects to both the chat app and the agents app. It forwards user messages to the agent workflow and sends responses back to chat.
3. **AI workers** - Agent processes that receive tasks via the Handoff pattern, call an LLM, and return responses. They can be scaled horizontally.

## Step 1: Install Dependencies

```bash [Terminal]
npm install @nolag/chat @nolag/agents @nolag/js-sdk
```

## Step 2: Set Up Apps in the Portal

Create two apps in your NoLag project:

1. A **Chat** Blueprint app (slug: `support-chat`)
2. An **Agents** Blueprint app (slug: `support-agents`)

Create three actors:

- **Bridge actor** - granted access to both apps
- **Worker actor** - granted access to the agents app
- **User actors** - granted access to the chat app (one per user, or use dynamic tokens)

## Step 3: Build the Bridge

The bridge is the key piece. It connects to both apps simultaneously and translates between chat messages and agent tasks:

```typescript [bridge.ts]
import { NoLagChat } from "@nolag/chat";
import { NoLagAgents } from "@nolag/agents";

// The bridge actor connects to both apps
const chat = new NoLagChat(BRIDGE_CHAT_TOKEN, {
  user: { id: "bot", name: "Support Bot", avatar: "/bot.png" },
});
const agents = new NoLagAgents(BRIDGE_AGENT_TOKEN);

await Promise.all([chat.connect(), agents.connect()]);

const chatRoom = chat.joinRoom("support");
const agentRoom = agents.joinRoom("support-workflow");

// Forward user messages to the agent workflow
chatRoom.on("message", async (msg) => {
  // Don't process our own messages
  if (msg.sender.id === "bot") return;

  // Show typing indicator while agent works
  chatRoom.startTyping();

  await agentRoom.handoff.dispatch({
    type: "respond",
    payload: {
      userMessage: msg.text,
      userId: msg.sender.id,
      history: chatRoom.getMessages().slice(-10),
    },
    capabilities: ["customer-support"],
  });
});

// Forward agent responses back to chat
agentRoom.handoff.onResult(async (result) => {
  chatRoom.stopTyping();
  await chatRoom.sendMessage(result.data.response);
});
```

The bridge shows typing indicators while the agent is processing, giving users a natural chat experience. Message history is passed to the agent so it has conversation context.

## Step 4: Build the AI Worker

Workers register their capabilities and process incoming tasks. You can use any LLM - OpenAI, Anthropic, a local model, or a custom pipeline:

```typescript [worker.ts]
import { NoLagAgents } from "@nolag/agents";

const agents = new NoLagAgents(WORKER_TOKEN);
await agents.connect();

const room = agents.joinRoom("support-workflow");

room.handoff.register({
  capabilities: ["customer-support"],
  concurrency: 5,
});

room.handoff.onTask(async (task) => {
  const { userMessage, history } = task.payload;

  // Call your LLM of choice
  const response = await openai.chat.completions.create({
    model: "gpt-4",
    messages: [
      { role: "system", content: "You are a helpful customer support agent." },
      ...history.map((m: any) => ({
        role: m.sender.id === "bot" ? "assistant" : "user",
        content: m.text,
      })),
      { role: "user", content: userMessage },
    ],
  });

  await task.complete({
    response: response.choices[0].message.content,
  });
});
```

Workers are stateless and horizontally scalable. Run multiple instances and they'll automatically load-balance tasks via the Handoff pattern.

## Step 5: Add Escalation

Use the Approve pattern to escalate sensitive conversations to a human agent. The worker detects negative sentiment and requests approval before responding:

```typescript [escalation.ts]
// In the worker, detect when to escalate
room.handoff.onTask(async (task) => {
  const { userMessage } = task.payload;
  const sentiment = await analyzeSentiment(userMessage);

  if (sentiment.score < -0.7) {
    // Escalate: request human approval before responding
    const approval = await room.approve.request({
      action: "escalate-to-human",
      description: "Negative sentiment detected. Review before responding.",
      payload: { userMessage, suggestedResponse: "..." },
    });

    if (approval.approved) {
      // Human may have modified the response
      await task.complete({
        response: approval.modifications?.response || approval.payload.suggestedResponse,
      });
    }
  } else {
    // Normal LLM response
    const response = await generateResponse(task.payload);
    await task.complete({ response });
  }
});
```

## Step 6: Monitor Everything

The Observe pattern gives you full visibility into the chatbot's performance:

```typescript [monitor.ts]
const room = agents.joinRoom("support-workflow");

// Track metrics
let totalQueries = 0;
let avgResponseTime = 0;

room.observe.on("task:completed", (event) => {
  totalQueries++;
  avgResponseTime = (avgResponseTime * (totalQueries - 1) + event.data.duration) / totalQueries;
  console.log(`Avg response: ${avgResponseTime}ms | Total: ${totalQueries}`);
});

room.observe.on("task:failed", (event) => {
  console.error("Agent failed:", event.data.error);
});

// Monitor agent health
const onlineAgents = room.getPresence();
console.log("Workers online:", onlineAgents.length);
```

## What You Get

- A chat interface with typing indicators and presence - users don't know they're talking to an AI
- Horizontally scalable AI workers with automatic load balancing
- Human escalation for sensitive conversations
- Full observability: response times, error rates, agent health
- Persistent message history with replay on reconnect
- Per-topic access control separating user traffic from agent traffic

The entire system runs on NoLag's real-time infrastructure. No Redis queues, no custom WebSocket servers, no separate monitoring stack.

[Get started with @nolag/agents →](/docs/agents/getting-started)
