[{"data":1,"prerenderedAt":1578},["ShallowReactive",2],{"blog:/blog/building-agentic-chatbot":3},{"id":4,"title":5,"author":6,"body":7,"category":1567,"date":1568,"description":1569,"excerpt":1570,"extension":1571,"meta":1572,"navigation":205,"path":1573,"readTime":1574,"seo":1575,"stem":1576,"__hash__":1577},"blog/blog/building-agentic-chatbot.md","Building an Agentic Chatbot with NoLag","Henco Burger",{"type":8,"value":9,"toc":1557},"minimark",[10,23,28,31,57,61,96,100,103,125,128,149,153,156,646,649,653,656,997,1000,1004,1007,1255,1259,1262,1519,1523,1543,1546,1553],[11,12,13,14,18,19,22],"p",{},"This tutorial walks through building a customer support chatbot that combines two NoLag Blueprints: ",[15,16,17],"code",{},"@nolag/chat"," for the user-facing interface and ",[15,20,21],{},"@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.",[24,25,27],"h2",{"id":26},"architecture-overview","Architecture Overview",[11,29,30],{},"The system has three components:",[32,33,34,45,51],"ol",{},[35,36,37,41,42,44],"li",{},[38,39,40],"strong",{},"Chat frontend"," - Users interact with a standard chat room powered by ",[15,43,17],{},". They see typing indicators, message history, and presence.",[35,46,47,50],{},[38,48,49],{},"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.",[35,52,53,56],{},[38,54,55],{},"AI workers"," - Agent processes that receive tasks via the Handoff pattern, call an LLM, and return responses. They can be scaled horizontally.",[24,58,60],{"id":59},"step-1-install-dependencies","Step 1: Install Dependencies",[62,63,64],"code-tabs",{},[65,66,72],"pre",{"className":67,"code":68,"filename":69,"language":70,"meta":71,"style":71},"language-bash shiki shiki-themes github-light github-dark","npm install @nolag/chat @nolag/agents @nolag/js-sdk\n","Terminal","bash","",[15,73,74],{"__ignoreMap":71},[75,76,79,83,87,90,93],"span",{"class":77,"line":78},"line",1,[75,80,82],{"class":81},"sScJk","npm",[75,84,86],{"class":85},"sZZnC"," install",[75,88,89],{"class":85}," @nolag/chat",[75,91,92],{"class":85}," @nolag/agents",[75,94,95],{"class":85}," @nolag/js-sdk\n",[24,97,99],{"id":98},"step-2-set-up-apps-in-the-portal","Step 2: Set Up Apps in the Portal",[11,101,102],{},"Create two apps in your NoLag project:",[32,104,105,116],{},[35,106,107,108,111,112,115],{},"A ",[38,109,110],{},"Chat"," Blueprint app (slug: ",[15,113,114],{},"support-chat",")",[35,117,118,119,111,122,115],{},"An ",[38,120,121],{},"Agents",[15,123,124],{},"support-agents",[11,126,127],{},"Create three actors:",[129,130,131,137,143],"ul",{},[35,132,133,136],{},[38,134,135],{},"Bridge actor"," - granted access to both apps",[35,138,139,142],{},[38,140,141],{},"Worker actor"," - granted access to the agents app",[35,144,145,148],{},[38,146,147],{},"User actors"," - granted access to the chat app (one per user, or use dynamic tokens)",[24,150,152],{"id":151},"step-3-build-the-bridge","Step 3: Build the Bridge",[11,154,155],{},"The bridge is the key piece. It connects to both apps simultaneously and translates between chat messages and agent tasks:",[62,157,158],{},[65,159,164],{"className":160,"code":161,"filename":162,"language":163,"meta":71,"style":71},"language-typescript shiki shiki-themes github-light github-dark","import { NoLagChat } from \"@nolag/chat\";\nimport { NoLagAgents } from \"@nolag/agents\";\n\n// The bridge actor connects to both apps\nconst chat = new NoLagChat(BRIDGE_CHAT_TOKEN, {\n  user: { id: \"bot\", name: \"Support Bot\", avatar: \"/bot.png\" },\n});\nconst agents = new NoLagAgents(BRIDGE_AGENT_TOKEN);\n\nawait Promise.all([chat.connect(), agents.connect()]);\n\nconst chatRoom = chat.joinRoom(\"support\");\nconst agentRoom = agents.joinRoom(\"support-workflow\");\n\n// Forward user messages to the agent workflow\nchatRoom.on(\"message\", async (msg) => {\n  // Don't process our own messages\n  if (msg.sender.id === \"bot\") return;\n\n  // Show typing indicator while agent works\n  chatRoom.startTyping();\n\n  await agentRoom.handoff.dispatch({\n    type: \"respond\",\n    payload: {\n      userMessage: msg.text,\n      userId: msg.sender.id,\n      history: chatRoom.getMessages().slice(-10),\n    },\n    capabilities: [\"customer-support\"],\n  });\n});\n\n// Forward agent responses back to chat\nagentRoom.handoff.onResult(async (result) => {\n  chatRoom.stopTyping();\n  await chatRoom.sendMessage(result.data.response);\n});\n","bridge.ts","typescript",[15,165,166,185,200,207,214,242,266,272,295,300,329,334,357,379,384,390,426,432,454,459,465,477,482,497,509,515,521,527,553,559,571,577,582,587,593,617,627,641],{"__ignoreMap":71},[75,167,168,172,176,179,182],{"class":77,"line":78},[75,169,171],{"class":170},"szBVR","import",[75,173,175],{"class":174},"sVt8B"," { NoLagChat } ",[75,177,178],{"class":170},"from",[75,180,181],{"class":85}," \"@nolag/chat\"",[75,183,184],{"class":174},";\n",[75,186,188,190,193,195,198],{"class":77,"line":187},2,[75,189,171],{"class":170},[75,191,192],{"class":174}," { NoLagAgents } ",[75,194,178],{"class":170},[75,196,197],{"class":85}," \"@nolag/agents\"",[75,199,184],{"class":174},[75,201,203],{"class":77,"line":202},3,[75,204,206],{"emptyLinePlaceholder":205},true,"\n",[75,208,210],{"class":77,"line":209},4,[75,211,213],{"class":212},"sJ8bj","// The bridge actor connects to both apps\n",[75,215,217,220,224,227,230,233,236,239],{"class":77,"line":216},5,[75,218,219],{"class":170},"const",[75,221,223],{"class":222},"sj4cs"," chat",[75,225,226],{"class":170}," =",[75,228,229],{"class":170}," new",[75,231,232],{"class":81}," NoLagChat",[75,234,235],{"class":174},"(",[75,237,238],{"class":222},"BRIDGE_CHAT_TOKEN",[75,240,241],{"class":174},", {\n",[75,243,245,248,251,254,257,260,263],{"class":77,"line":244},6,[75,246,247],{"class":174},"  user: { id: ",[75,249,250],{"class":85},"\"bot\"",[75,252,253],{"class":174},", name: ",[75,255,256],{"class":85},"\"Support Bot\"",[75,258,259],{"class":174},", avatar: ",[75,261,262],{"class":85},"\"/bot.png\"",[75,264,265],{"class":174}," },\n",[75,267,269],{"class":77,"line":268},7,[75,270,271],{"class":174},"});\n",[75,273,275,277,280,282,284,287,289,292],{"class":77,"line":274},8,[75,276,219],{"class":170},[75,278,279],{"class":222}," agents",[75,281,226],{"class":170},[75,283,229],{"class":170},[75,285,286],{"class":81}," NoLagAgents",[75,288,235],{"class":174},[75,290,291],{"class":222},"BRIDGE_AGENT_TOKEN",[75,293,294],{"class":174},");\n",[75,296,298],{"class":77,"line":297},9,[75,299,206],{"emptyLinePlaceholder":205},[75,301,303,306,309,312,315,318,321,324,326],{"class":77,"line":302},10,[75,304,305],{"class":170},"await",[75,307,308],{"class":222}," Promise",[75,310,311],{"class":174},".",[75,313,314],{"class":81},"all",[75,316,317],{"class":174},"([chat.",[75,319,320],{"class":81},"connect",[75,322,323],{"class":174},"(), agents.",[75,325,320],{"class":81},[75,327,328],{"class":174},"()]);\n",[75,330,332],{"class":77,"line":331},11,[75,333,206],{"emptyLinePlaceholder":205},[75,335,337,339,342,344,347,350,352,355],{"class":77,"line":336},12,[75,338,219],{"class":170},[75,340,341],{"class":222}," chatRoom",[75,343,226],{"class":170},[75,345,346],{"class":174}," chat.",[75,348,349],{"class":81},"joinRoom",[75,351,235],{"class":174},[75,353,354],{"class":85},"\"support\"",[75,356,294],{"class":174},[75,358,360,362,365,367,370,372,374,377],{"class":77,"line":359},13,[75,361,219],{"class":170},[75,363,364],{"class":222}," agentRoom",[75,366,226],{"class":170},[75,368,369],{"class":174}," agents.",[75,371,349],{"class":81},[75,373,235],{"class":174},[75,375,376],{"class":85},"\"support-workflow\"",[75,378,294],{"class":174},[75,380,382],{"class":77,"line":381},14,[75,383,206],{"emptyLinePlaceholder":205},[75,385,387],{"class":77,"line":386},15,[75,388,389],{"class":212},"// Forward user messages to the agent workflow\n",[75,391,393,396,399,401,404,407,410,413,417,420,423],{"class":77,"line":392},16,[75,394,395],{"class":174},"chatRoom.",[75,397,398],{"class":81},"on",[75,400,235],{"class":174},[75,402,403],{"class":85},"\"message\"",[75,405,406],{"class":174},", ",[75,408,409],{"class":170},"async",[75,411,412],{"class":174}," (",[75,414,416],{"class":415},"s4XuR","msg",[75,418,419],{"class":174},") ",[75,421,422],{"class":170},"=>",[75,424,425],{"class":174}," {\n",[75,427,429],{"class":77,"line":428},17,[75,430,431],{"class":212},"  // Don't process our own messages\n",[75,433,435,438,441,444,447,449,452],{"class":77,"line":434},18,[75,436,437],{"class":170},"  if",[75,439,440],{"class":174}," (msg.sender.id ",[75,442,443],{"class":170},"===",[75,445,446],{"class":85}," \"bot\"",[75,448,419],{"class":174},[75,450,451],{"class":170},"return",[75,453,184],{"class":174},[75,455,457],{"class":77,"line":456},19,[75,458,206],{"emptyLinePlaceholder":205},[75,460,462],{"class":77,"line":461},20,[75,463,464],{"class":212},"  // Show typing indicator while agent works\n",[75,466,468,471,474],{"class":77,"line":467},21,[75,469,470],{"class":174},"  chatRoom.",[75,472,473],{"class":81},"startTyping",[75,475,476],{"class":174},"();\n",[75,478,480],{"class":77,"line":479},22,[75,481,206],{"emptyLinePlaceholder":205},[75,483,485,488,491,494],{"class":77,"line":484},23,[75,486,487],{"class":170},"  await",[75,489,490],{"class":174}," agentRoom.handoff.",[75,492,493],{"class":81},"dispatch",[75,495,496],{"class":174},"({\n",[75,498,500,503,506],{"class":77,"line":499},24,[75,501,502],{"class":174},"    type: ",[75,504,505],{"class":85},"\"respond\"",[75,507,508],{"class":174},",\n",[75,510,512],{"class":77,"line":511},25,[75,513,514],{"class":174},"    payload: {\n",[75,516,518],{"class":77,"line":517},26,[75,519,520],{"class":174},"      userMessage: msg.text,\n",[75,522,524],{"class":77,"line":523},27,[75,525,526],{"class":174},"      userId: msg.sender.id,\n",[75,528,530,533,536,539,542,544,547,550],{"class":77,"line":529},28,[75,531,532],{"class":174},"      history: chatRoom.",[75,534,535],{"class":81},"getMessages",[75,537,538],{"class":174},"().",[75,540,541],{"class":81},"slice",[75,543,235],{"class":174},[75,545,546],{"class":170},"-",[75,548,549],{"class":222},"10",[75,551,552],{"class":174},"),\n",[75,554,556],{"class":77,"line":555},29,[75,557,558],{"class":174},"    },\n",[75,560,562,565,568],{"class":77,"line":561},30,[75,563,564],{"class":174},"    capabilities: [",[75,566,567],{"class":85},"\"customer-support\"",[75,569,570],{"class":174},"],\n",[75,572,574],{"class":77,"line":573},31,[75,575,576],{"class":174},"  });\n",[75,578,580],{"class":77,"line":579},32,[75,581,271],{"class":174},[75,583,585],{"class":77,"line":584},33,[75,586,206],{"emptyLinePlaceholder":205},[75,588,590],{"class":77,"line":589},34,[75,591,592],{"class":212},"// Forward agent responses back to chat\n",[75,594,596,599,602,604,606,608,611,613,615],{"class":77,"line":595},35,[75,597,598],{"class":174},"agentRoom.handoff.",[75,600,601],{"class":81},"onResult",[75,603,235],{"class":174},[75,605,409],{"class":170},[75,607,412],{"class":174},[75,609,610],{"class":415},"result",[75,612,419],{"class":174},[75,614,422],{"class":170},[75,616,425],{"class":174},[75,618,620,622,625],{"class":77,"line":619},36,[75,621,470],{"class":174},[75,623,624],{"class":81},"stopTyping",[75,626,476],{"class":174},[75,628,630,632,635,638],{"class":77,"line":629},37,[75,631,487],{"class":170},[75,633,634],{"class":174}," chatRoom.",[75,636,637],{"class":81},"sendMessage",[75,639,640],{"class":174},"(result.data.response);\n",[75,642,644],{"class":77,"line":643},38,[75,645,271],{"class":174},[11,647,648],{},"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.",[24,650,652],{"id":651},"step-4-build-the-ai-worker","Step 4: Build the AI Worker",[11,654,655],{},"Workers register their capabilities and process incoming tasks. You can use any LLM - OpenAI, Anthropic, a local model, or a custom pipeline:",[62,657,658],{},[65,659,662],{"className":160,"code":660,"filename":661,"language":163,"meta":71,"style":71},"import { NoLagAgents } from \"@nolag/agents\";\n\nconst agents = new NoLagAgents(WORKER_TOKEN);\nawait agents.connect();\n\nconst room = agents.joinRoom(\"support-workflow\");\n\nroom.handoff.register({\n  capabilities: [\"customer-support\"],\n  concurrency: 5,\n});\n\nroom.handoff.onTask(async (task) => {\n  const { userMessage, history } = task.payload;\n\n  // Call your LLM of choice\n  const response = await openai.chat.completions.create({\n    model: \"gpt-4\",\n    messages: [\n      { role: \"system\", content: \"You are a helpful customer support agent.\" },\n      ...history.map((m: any) => ({\n        role: m.sender.id === \"bot\" ? \"assistant\" : \"user\",\n        content: m.text,\n      })),\n      { role: \"user\", content: userMessage },\n    ],\n  });\n\n  await task.complete({\n    response: response.choices[0].message.content,\n  });\n});\n","worker.ts",[15,663,664,676,680,699,709,713,732,736,746,755,765,769,773,795,820,824,829,849,859,864,880,910,933,938,943,953,958,962,966,978,989,993],{"__ignoreMap":71},[75,665,666,668,670,672,674],{"class":77,"line":78},[75,667,171],{"class":170},[75,669,192],{"class":174},[75,671,178],{"class":170},[75,673,197],{"class":85},[75,675,184],{"class":174},[75,677,678],{"class":77,"line":187},[75,679,206],{"emptyLinePlaceholder":205},[75,681,682,684,686,688,690,692,694,697],{"class":77,"line":202},[75,683,219],{"class":170},[75,685,279],{"class":222},[75,687,226],{"class":170},[75,689,229],{"class":170},[75,691,286],{"class":81},[75,693,235],{"class":174},[75,695,696],{"class":222},"WORKER_TOKEN",[75,698,294],{"class":174},[75,700,701,703,705,707],{"class":77,"line":209},[75,702,305],{"class":170},[75,704,369],{"class":174},[75,706,320],{"class":81},[75,708,476],{"class":174},[75,710,711],{"class":77,"line":216},[75,712,206],{"emptyLinePlaceholder":205},[75,714,715,717,720,722,724,726,728,730],{"class":77,"line":244},[75,716,219],{"class":170},[75,718,719],{"class":222}," room",[75,721,226],{"class":170},[75,723,369],{"class":174},[75,725,349],{"class":81},[75,727,235],{"class":174},[75,729,376],{"class":85},[75,731,294],{"class":174},[75,733,734],{"class":77,"line":268},[75,735,206],{"emptyLinePlaceholder":205},[75,737,738,741,744],{"class":77,"line":274},[75,739,740],{"class":174},"room.handoff.",[75,742,743],{"class":81},"register",[75,745,496],{"class":174},[75,747,748,751,753],{"class":77,"line":297},[75,749,750],{"class":174},"  capabilities: [",[75,752,567],{"class":85},[75,754,570],{"class":174},[75,756,757,760,763],{"class":77,"line":302},[75,758,759],{"class":174},"  concurrency: ",[75,761,762],{"class":222},"5",[75,764,508],{"class":174},[75,766,767],{"class":77,"line":331},[75,768,271],{"class":174},[75,770,771],{"class":77,"line":336},[75,772,206],{"emptyLinePlaceholder":205},[75,774,775,777,780,782,784,786,789,791,793],{"class":77,"line":359},[75,776,740],{"class":174},[75,778,779],{"class":81},"onTask",[75,781,235],{"class":174},[75,783,409],{"class":170},[75,785,412],{"class":174},[75,787,788],{"class":415},"task",[75,790,419],{"class":174},[75,792,422],{"class":170},[75,794,425],{"class":174},[75,796,797,800,803,806,808,811,814,817],{"class":77,"line":381},[75,798,799],{"class":170},"  const",[75,801,802],{"class":174}," { ",[75,804,805],{"class":222},"userMessage",[75,807,406],{"class":174},[75,809,810],{"class":222},"history",[75,812,813],{"class":174}," } ",[75,815,816],{"class":170},"=",[75,818,819],{"class":174}," task.payload;\n",[75,821,822],{"class":77,"line":386},[75,823,206],{"emptyLinePlaceholder":205},[75,825,826],{"class":77,"line":392},[75,827,828],{"class":212},"  // Call your LLM of choice\n",[75,830,831,833,836,838,841,844,847],{"class":77,"line":428},[75,832,799],{"class":170},[75,834,835],{"class":222}," response",[75,837,226],{"class":170},[75,839,840],{"class":170}," await",[75,842,843],{"class":174}," openai.chat.completions.",[75,845,846],{"class":81},"create",[75,848,496],{"class":174},[75,850,851,854,857],{"class":77,"line":434},[75,852,853],{"class":174},"    model: ",[75,855,856],{"class":85},"\"gpt-4\"",[75,858,508],{"class":174},[75,860,861],{"class":77,"line":456},[75,862,863],{"class":174},"    messages: [\n",[75,865,866,869,872,875,878],{"class":77,"line":461},[75,867,868],{"class":174},"      { role: ",[75,870,871],{"class":85},"\"system\"",[75,873,874],{"class":174},", content: ",[75,876,877],{"class":85},"\"You are a helpful customer support agent.\"",[75,879,265],{"class":174},[75,881,882,885,888,891,894,897,900,903,905,907],{"class":77,"line":467},[75,883,884],{"class":170},"      ...",[75,886,887],{"class":174},"history.",[75,889,890],{"class":81},"map",[75,892,893],{"class":174},"((",[75,895,896],{"class":415},"m",[75,898,899],{"class":170},":",[75,901,902],{"class":222}," any",[75,904,419],{"class":174},[75,906,422],{"class":170},[75,908,909],{"class":174}," ({\n",[75,911,912,915,917,919,922,925,928,931],{"class":77,"line":479},[75,913,914],{"class":174},"        role: m.sender.id ",[75,916,443],{"class":170},[75,918,446],{"class":85},[75,920,921],{"class":170}," ?",[75,923,924],{"class":85}," \"assistant\"",[75,926,927],{"class":170}," :",[75,929,930],{"class":85}," \"user\"",[75,932,508],{"class":174},[75,934,935],{"class":77,"line":484},[75,936,937],{"class":174},"        content: m.text,\n",[75,939,940],{"class":77,"line":499},[75,941,942],{"class":174},"      })),\n",[75,944,945,947,950],{"class":77,"line":511},[75,946,868],{"class":174},[75,948,949],{"class":85},"\"user\"",[75,951,952],{"class":174},", content: userMessage },\n",[75,954,955],{"class":77,"line":517},[75,956,957],{"class":174},"    ],\n",[75,959,960],{"class":77,"line":523},[75,961,576],{"class":174},[75,963,964],{"class":77,"line":529},[75,965,206],{"emptyLinePlaceholder":205},[75,967,968,970,973,976],{"class":77,"line":555},[75,969,487],{"class":170},[75,971,972],{"class":174}," task.",[75,974,975],{"class":81},"complete",[75,977,496],{"class":174},[75,979,980,983,986],{"class":77,"line":561},[75,981,982],{"class":174},"    response: response.choices[",[75,984,985],{"class":222},"0",[75,987,988],{"class":174},"].message.content,\n",[75,990,991],{"class":77,"line":573},[75,992,576],{"class":174},[75,994,995],{"class":77,"line":579},[75,996,271],{"class":174},[11,998,999],{},"Workers are stateless and horizontally scalable. Run multiple instances and they'll automatically load-balance tasks via the Handoff pattern.",[24,1001,1003],{"id":1002},"step-5-add-escalation","Step 5: Add Escalation",[11,1005,1006],{},"Use the Approve pattern to escalate sensitive conversations to a human agent. The worker detects negative sentiment and requests approval before responding:",[62,1008,1009],{},[65,1010,1013],{"className":160,"code":1011,"filename":1012,"language":163,"meta":71,"style":71},"// In the worker, detect when to escalate\nroom.handoff.onTask(async (task) => {\n  const { userMessage } = task.payload;\n  const sentiment = await analyzeSentiment(userMessage);\n\n  if (sentiment.score \u003C -0.7) {\n    // Escalate: request human approval before responding\n    const approval = await room.approve.request({\n      action: \"escalate-to-human\",\n      description: \"Negative sentiment detected. Review before responding.\",\n      payload: { userMessage, suggestedResponse: \"...\" },\n    });\n\n    if (approval.approved) {\n      // Human may have modified the response\n      await task.complete({\n        response: approval.modifications?.response || approval.payload.suggestedResponse,\n      });\n    }\n  } else {\n    // Normal LLM response\n    const response = await generateResponse(task.payload);\n    await task.complete({ response });\n  }\n});\n","escalation.ts",[15,1014,1015,1020,1040,1054,1071,1075,1094,1099,1119,1129,1139,1149,1154,1158,1166,1171,1182,1193,1198,1203,1213,1218,1234,1246,1251],{"__ignoreMap":71},[75,1016,1017],{"class":77,"line":78},[75,1018,1019],{"class":212},"// In the worker, detect when to escalate\n",[75,1021,1022,1024,1026,1028,1030,1032,1034,1036,1038],{"class":77,"line":187},[75,1023,740],{"class":174},[75,1025,779],{"class":81},[75,1027,235],{"class":174},[75,1029,409],{"class":170},[75,1031,412],{"class":174},[75,1033,788],{"class":415},[75,1035,419],{"class":174},[75,1037,422],{"class":170},[75,1039,425],{"class":174},[75,1041,1042,1044,1046,1048,1050,1052],{"class":77,"line":202},[75,1043,799],{"class":170},[75,1045,802],{"class":174},[75,1047,805],{"class":222},[75,1049,813],{"class":174},[75,1051,816],{"class":170},[75,1053,819],{"class":174},[75,1055,1056,1058,1061,1063,1065,1068],{"class":77,"line":209},[75,1057,799],{"class":170},[75,1059,1060],{"class":222}," sentiment",[75,1062,226],{"class":170},[75,1064,840],{"class":170},[75,1066,1067],{"class":81}," analyzeSentiment",[75,1069,1070],{"class":174},"(userMessage);\n",[75,1072,1073],{"class":77,"line":216},[75,1074,206],{"emptyLinePlaceholder":205},[75,1076,1077,1079,1082,1085,1088,1091],{"class":77,"line":244},[75,1078,437],{"class":170},[75,1080,1081],{"class":174}," (sentiment.score ",[75,1083,1084],{"class":170},"\u003C",[75,1086,1087],{"class":170}," -",[75,1089,1090],{"class":222},"0.7",[75,1092,1093],{"class":174},") {\n",[75,1095,1096],{"class":77,"line":268},[75,1097,1098],{"class":212},"    // Escalate: request human approval before responding\n",[75,1100,1101,1104,1107,1109,1111,1114,1117],{"class":77,"line":274},[75,1102,1103],{"class":170},"    const",[75,1105,1106],{"class":222}," approval",[75,1108,226],{"class":170},[75,1110,840],{"class":170},[75,1112,1113],{"class":174}," room.approve.",[75,1115,1116],{"class":81},"request",[75,1118,496],{"class":174},[75,1120,1121,1124,1127],{"class":77,"line":297},[75,1122,1123],{"class":174},"      action: ",[75,1125,1126],{"class":85},"\"escalate-to-human\"",[75,1128,508],{"class":174},[75,1130,1131,1134,1137],{"class":77,"line":302},[75,1132,1133],{"class":174},"      description: ",[75,1135,1136],{"class":85},"\"Negative sentiment detected. Review before responding.\"",[75,1138,508],{"class":174},[75,1140,1141,1144,1147],{"class":77,"line":331},[75,1142,1143],{"class":174},"      payload: { userMessage, suggestedResponse: ",[75,1145,1146],{"class":85},"\"...\"",[75,1148,265],{"class":174},[75,1150,1151],{"class":77,"line":336},[75,1152,1153],{"class":174},"    });\n",[75,1155,1156],{"class":77,"line":359},[75,1157,206],{"emptyLinePlaceholder":205},[75,1159,1160,1163],{"class":77,"line":381},[75,1161,1162],{"class":170},"    if",[75,1164,1165],{"class":174}," (approval.approved) {\n",[75,1167,1168],{"class":77,"line":386},[75,1169,1170],{"class":212},"      // Human may have modified the response\n",[75,1172,1173,1176,1178,1180],{"class":77,"line":392},[75,1174,1175],{"class":170},"      await",[75,1177,972],{"class":174},[75,1179,975],{"class":81},[75,1181,496],{"class":174},[75,1183,1184,1187,1190],{"class":77,"line":428},[75,1185,1186],{"class":174},"        response: approval.modifications?.response ",[75,1188,1189],{"class":170},"||",[75,1191,1192],{"class":174}," approval.payload.suggestedResponse,\n",[75,1194,1195],{"class":77,"line":434},[75,1196,1197],{"class":174},"      });\n",[75,1199,1200],{"class":77,"line":456},[75,1201,1202],{"class":174},"    }\n",[75,1204,1205,1208,1211],{"class":77,"line":461},[75,1206,1207],{"class":174},"  } ",[75,1209,1210],{"class":170},"else",[75,1212,425],{"class":174},[75,1214,1215],{"class":77,"line":467},[75,1216,1217],{"class":212},"    // Normal LLM response\n",[75,1219,1220,1222,1224,1226,1228,1231],{"class":77,"line":479},[75,1221,1103],{"class":170},[75,1223,835],{"class":222},[75,1225,226],{"class":170},[75,1227,840],{"class":170},[75,1229,1230],{"class":81}," generateResponse",[75,1232,1233],{"class":174},"(task.payload);\n",[75,1235,1236,1239,1241,1243],{"class":77,"line":484},[75,1237,1238],{"class":170},"    await",[75,1240,972],{"class":174},[75,1242,975],{"class":81},[75,1244,1245],{"class":174},"({ response });\n",[75,1247,1248],{"class":77,"line":499},[75,1249,1250],{"class":174},"  }\n",[75,1252,1253],{"class":77,"line":511},[75,1254,271],{"class":174},[24,1256,1258],{"id":1257},"step-6-monitor-everything","Step 6: Monitor Everything",[11,1260,1261],{},"The Observe pattern gives you full visibility into the chatbot's performance:",[62,1263,1264],{},[65,1265,1268],{"className":160,"code":1266,"filename":1267,"language":163,"meta":71,"style":71},"const room = agents.joinRoom(\"support-workflow\");\n\n// Track metrics\nlet totalQueries = 0;\nlet avgResponseTime = 0;\n\nroom.observe.on(\"task:completed\", (event) => {\n  totalQueries++;\n  avgResponseTime = (avgResponseTime * (totalQueries - 1) + event.data.duration) / totalQueries;\n  console.log(`Avg response: ${avgResponseTime}ms | Total: ${totalQueries}`);\n});\n\nroom.observe.on(\"task:failed\", (event) => {\n  console.error(\"Agent failed:\", event.data.error);\n});\n\n// Monitor agent health\nconst onlineAgents = room.getPresence();\nconsole.log(\"Workers online:\", onlineAgents.length);\n","monitor.ts",[15,1269,1270,1288,1292,1297,1312,1325,1329,1353,1363,1398,1425,1429,1433,1454,1469,1473,1477,1482,1499],{"__ignoreMap":71},[75,1271,1272,1274,1276,1278,1280,1282,1284,1286],{"class":77,"line":78},[75,1273,219],{"class":170},[75,1275,719],{"class":222},[75,1277,226],{"class":170},[75,1279,369],{"class":174},[75,1281,349],{"class":81},[75,1283,235],{"class":174},[75,1285,376],{"class":85},[75,1287,294],{"class":174},[75,1289,1290],{"class":77,"line":187},[75,1291,206],{"emptyLinePlaceholder":205},[75,1293,1294],{"class":77,"line":202},[75,1295,1296],{"class":212},"// Track metrics\n",[75,1298,1299,1302,1305,1307,1310],{"class":77,"line":209},[75,1300,1301],{"class":170},"let",[75,1303,1304],{"class":174}," totalQueries ",[75,1306,816],{"class":170},[75,1308,1309],{"class":222}," 0",[75,1311,184],{"class":174},[75,1313,1314,1316,1319,1321,1323],{"class":77,"line":216},[75,1315,1301],{"class":170},[75,1317,1318],{"class":174}," avgResponseTime ",[75,1320,816],{"class":170},[75,1322,1309],{"class":222},[75,1324,184],{"class":174},[75,1326,1327],{"class":77,"line":244},[75,1328,206],{"emptyLinePlaceholder":205},[75,1330,1331,1334,1336,1338,1341,1344,1347,1349,1351],{"class":77,"line":268},[75,1332,1333],{"class":174},"room.observe.",[75,1335,398],{"class":81},[75,1337,235],{"class":174},[75,1339,1340],{"class":85},"\"task:completed\"",[75,1342,1343],{"class":174},", (",[75,1345,1346],{"class":415},"event",[75,1348,419],{"class":174},[75,1350,422],{"class":170},[75,1352,425],{"class":174},[75,1354,1355,1358,1361],{"class":77,"line":274},[75,1356,1357],{"class":174},"  totalQueries",[75,1359,1360],{"class":170},"++",[75,1362,184],{"class":174},[75,1364,1365,1368,1370,1373,1376,1379,1381,1384,1386,1389,1392,1395],{"class":77,"line":297},[75,1366,1367],{"class":174},"  avgResponseTime ",[75,1369,816],{"class":170},[75,1371,1372],{"class":174}," (avgResponseTime ",[75,1374,1375],{"class":170},"*",[75,1377,1378],{"class":174}," (totalQueries ",[75,1380,546],{"class":170},[75,1382,1383],{"class":222}," 1",[75,1385,419],{"class":174},[75,1387,1388],{"class":170},"+",[75,1390,1391],{"class":174}," event.data.duration) ",[75,1393,1394],{"class":170},"/",[75,1396,1397],{"class":174}," totalQueries;\n",[75,1399,1400,1403,1406,1408,1411,1414,1417,1420,1423],{"class":77,"line":302},[75,1401,1402],{"class":174},"  console.",[75,1404,1405],{"class":81},"log",[75,1407,235],{"class":174},[75,1409,1410],{"class":85},"`Avg response: ${",[75,1412,1413],{"class":174},"avgResponseTime",[75,1415,1416],{"class":85},"}ms | Total: ${",[75,1418,1419],{"class":174},"totalQueries",[75,1421,1422],{"class":85},"}`",[75,1424,294],{"class":174},[75,1426,1427],{"class":77,"line":331},[75,1428,271],{"class":174},[75,1430,1431],{"class":77,"line":336},[75,1432,206],{"emptyLinePlaceholder":205},[75,1434,1435,1437,1439,1441,1444,1446,1448,1450,1452],{"class":77,"line":359},[75,1436,1333],{"class":174},[75,1438,398],{"class":81},[75,1440,235],{"class":174},[75,1442,1443],{"class":85},"\"task:failed\"",[75,1445,1343],{"class":174},[75,1447,1346],{"class":415},[75,1449,419],{"class":174},[75,1451,422],{"class":170},[75,1453,425],{"class":174},[75,1455,1456,1458,1461,1463,1466],{"class":77,"line":381},[75,1457,1402],{"class":174},[75,1459,1460],{"class":81},"error",[75,1462,235],{"class":174},[75,1464,1465],{"class":85},"\"Agent failed:\"",[75,1467,1468],{"class":174},", event.data.error);\n",[75,1470,1471],{"class":77,"line":386},[75,1472,271],{"class":174},[75,1474,1475],{"class":77,"line":392},[75,1476,206],{"emptyLinePlaceholder":205},[75,1478,1479],{"class":77,"line":428},[75,1480,1481],{"class":212},"// Monitor agent health\n",[75,1483,1484,1486,1489,1491,1494,1497],{"class":77,"line":434},[75,1485,219],{"class":170},[75,1487,1488],{"class":222}," onlineAgents",[75,1490,226],{"class":170},[75,1492,1493],{"class":174}," room.",[75,1495,1496],{"class":81},"getPresence",[75,1498,476],{"class":174},[75,1500,1501,1504,1506,1508,1511,1514,1517],{"class":77,"line":456},[75,1502,1503],{"class":174},"console.",[75,1505,1405],{"class":81},[75,1507,235],{"class":174},[75,1509,1510],{"class":85},"\"Workers online:\"",[75,1512,1513],{"class":174},", onlineAgents.",[75,1515,1516],{"class":222},"length",[75,1518,294],{"class":174},[24,1520,1522],{"id":1521},"what-you-get","What You Get",[129,1524,1525,1528,1531,1534,1537,1540],{},[35,1526,1527],{},"A chat interface with typing indicators and presence - users don't know they're talking to an AI",[35,1529,1530],{},"Horizontally scalable AI workers with automatic load balancing",[35,1532,1533],{},"Human escalation for sensitive conversations",[35,1535,1536],{},"Full observability: response times, error rates, agent health",[35,1538,1539],{},"Persistent message history with replay on reconnect",[35,1541,1542],{},"Per-topic access control separating user traffic from agent traffic",[11,1544,1545],{},"The entire system runs on NoLag's real-time infrastructure. No Redis queues, no custom WebSocket servers, no separate monitoring stack.",[11,1547,1548],{},[1549,1550,1552],"a",{"href":1551},"/docs/agents/getting-started","Get started with @nolag/agents →",[1554,1555,1556],"style",{},"html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}",{"title":71,"searchDepth":187,"depth":187,"links":1558},[1559,1560,1561,1562,1563,1564,1565,1566],{"id":26,"depth":187,"text":27},{"id":59,"depth":187,"text":60},{"id":98,"depth":187,"text":99},{"id":151,"depth":187,"text":152},{"id":651,"depth":187,"text":652},{"id":1002,"depth":187,"text":1003},{"id":1257,"depth":187,"text":1258},{"id":1521,"depth":187,"text":1522},"AI Agents","2026-05-19","Step-by-step tutorial: combine @nolag/chat for the user-facing interface with @nolag/agents for AI-powered responses. Full code walkthrough.",null,"md",{},"/blog/building-agentic-chatbot","14 min read",{"title":5,"description":1569},"blog/building-agentic-chatbot","th_JnRxqIUb9J-nBlj4eu8v5yRhnJmVtDmRV7k5dT6c",1788160339717]