[{"data":1,"prerenderedAt":2448},["ShallowReactive",2],{"blog:/blog/building-agentic-chatbot":3},{"id":4,"title":5,"author":6,"body":7,"category":2437,"date":2438,"description":2439,"excerpt":2440,"extension":2441,"meta":2442,"navigation":269,"path":2443,"readTime":2444,"seo":2445,"stem":2446,"__hash__":2447},"blog/blog/building-agentic-chatbot.md","Building an Agentic Chatbot with NoLag","Henco Burger",{"type":8,"value":9,"toc":2427},"minimark",[10,23,28,31,57,61,96,100,103,131,158,161,198,202,205,868,886,890,893,1449,1456,1460,1463,1897,1900,1904,1914,2390,2394,2414,2417,2423],[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 context, sentiment-based escalation, and 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,119],{},[35,106,107,108,111,112,115,116],{},"A ",[38,109,110],{},"Chat"," Blueprint app (",[15,113,114],{},"nolag-chat-sdk","), named for example ",[15,117,118],{},"support-chat",[35,120,121,122,111,125,115,128],{},"An ",[38,123,124],{},"Agents",[15,126,127],{},"nolag-agents-sdk",[15,129,130],{},"support-agents",[11,132,133,134,137,138,141,142,145,146,149,150,153,154,157],{},"The slug you get back for each app has a random suffix (",[15,135,136],{},"support-chat-a3f9","); that suffixed slug is the ",[15,139,140],{},"appName"," you pass to each wrapper. Rooms never exist implicitly, so also create a ",[15,143,144],{},"support"," room in the chat app and a ",[15,147,148],{},"support-workflow"," room in the agents app (or use the seeded ",[15,151,152],{},"general"," and ",[15,155,156],{},"default-workflow"," rooms).",[11,159,160],{},"Create three kinds of actors:",[162,163,164,174,183],"ul",{},[35,165,166,169,170,173],{},[38,167,168],{},"Bridge actor"," (type ",[15,171,172],{},"orchestrator",") - granted access to both apps",[35,175,176,169,179,182],{},[38,177,178],{},"Worker actor",[15,180,181],{},"agent",") - granted access to the agents app",[35,184,185,169,188,191,192,197],{},[38,186,187],{},"User actors",[15,189,190],{},"user",") - granted access to the chat app (one per user, or use ",[193,194,196],"a",{"href":195},"/docs/client-tokens","client tokens",")",[24,199,201],{"id":200},"step-3-build-the-bridge","Step 3: Build the Bridge",[11,203,204],{},"The bridge is the key piece. One connection carries two wrappers, one per app, and the bridge translates between chat messages and agent tasks:",[62,206,207],{},[65,208,213],{"className":209,"code":210,"filename":211,"language":212,"meta":71,"style":71},"language-typescript shiki shiki-themes github-light github-dark","import { NoLag } from \"@nolag/js-sdk\";\nimport { NoLagChat } from \"@nolag/chat\";\nimport { NoLagAgents, Handoff } from \"@nolag/agents\";\n\n// One connection, two wrappers: the bridge actor has access to both apps\nconst client = NoLag(BRIDGE_TOKEN);\nconst chat = new NoLagChat({\n  client,\n  appName: CHAT_APP_SLUG, // the suffixed slug of the chat app\n  username: \"Support Bot\",\n  avatar: \"/bot.png\",\n});\nconst agents = new NoLagAgents({\n  client,\n  appName: AGENTS_APP_SLUG, // the suffixed slug of the agents app\n  agentId: \"bridge\",\n  presence: { name: \"bridge\", role: \"orchestrator\" },\n});\n\nawait client.connect();\nawait Promise.all([chat.ready(), agents.ready()]);\n\nconst chatRoom = chat.joinRoom(\"support\");\nconst agentRoom = agents.room(\"support-workflow\");\nconst handoff = new Handoff(agentRoom);\nconst botId = chat.localUser!.userId;\n\n// Forward user messages to the agent workflow. Publishers never receive\n// their own messages, so the bot's replies never come back through here.\nchatRoom.on(\"message\", async (msg) => {\n  // Show typing indicator while the worker thinks\n  chatRoom.startTyping();\n\n  const history = chatRoom.getMessages().slice(-10).map((m) => ({\n    role: m.userId === botId ? \"assistant\" : \"user\",\n    content: m.text,\n  }));\n\n  const result = await handoff.dispatch(\n    \"customer-support\",\n    { userMessage: msg.text, userId: msg.userId, history },\n    { waitForResult: true, timeout: 30000 },\n  );\n\n  chatRoom.stopTyping();\n  if (result && result.status === \"success\") {\n    chatRoom.sendMessage(String(result.payload.response));\n  }\n});\n","bridge.ts","typescript",[15,214,215,234,249,264,271,278,303,322,328,343,355,366,372,389,394,407,418,435,440,445,460,488,493,516,539,557,576,581,587,593,628,634,645,650,700,726,732,738,743,765,773,779,796,802,807,817,840,857,863],{"__ignoreMap":71},[75,216,217,221,225,228,231],{"class":77,"line":78},[75,218,220],{"class":219},"szBVR","import",[75,222,224],{"class":223},"sVt8B"," { NoLag } ",[75,226,227],{"class":219},"from",[75,229,230],{"class":85}," \"@nolag/js-sdk\"",[75,232,233],{"class":223},";\n",[75,235,237,239,242,244,247],{"class":77,"line":236},2,[75,238,220],{"class":219},[75,240,241],{"class":223}," { NoLagChat } ",[75,243,227],{"class":219},[75,245,246],{"class":85}," \"@nolag/chat\"",[75,248,233],{"class":223},[75,250,252,254,257,259,262],{"class":77,"line":251},3,[75,253,220],{"class":219},[75,255,256],{"class":223}," { NoLagAgents, Handoff } ",[75,258,227],{"class":219},[75,260,261],{"class":85}," \"@nolag/agents\"",[75,263,233],{"class":223},[75,265,267],{"class":77,"line":266},4,[75,268,270],{"emptyLinePlaceholder":269},true,"\n",[75,272,274],{"class":77,"line":273},5,[75,275,277],{"class":276},"sJ8bj","// One connection, two wrappers: the bridge actor has access to both apps\n",[75,279,281,284,288,291,294,297,300],{"class":77,"line":280},6,[75,282,283],{"class":219},"const",[75,285,287],{"class":286},"sj4cs"," client",[75,289,290],{"class":219}," =",[75,292,293],{"class":81}," NoLag",[75,295,296],{"class":223},"(",[75,298,299],{"class":286},"BRIDGE_TOKEN",[75,301,302],{"class":223},");\n",[75,304,306,308,311,313,316,319],{"class":77,"line":305},7,[75,307,283],{"class":219},[75,309,310],{"class":286}," chat",[75,312,290],{"class":219},[75,314,315],{"class":219}," new",[75,317,318],{"class":81}," NoLagChat",[75,320,321],{"class":223},"({\n",[75,323,325],{"class":77,"line":324},8,[75,326,327],{"class":223},"  client,\n",[75,329,331,334,337,340],{"class":77,"line":330},9,[75,332,333],{"class":223},"  appName: ",[75,335,336],{"class":286},"CHAT_APP_SLUG",[75,338,339],{"class":223},", ",[75,341,342],{"class":276},"// the suffixed slug of the chat app\n",[75,344,346,349,352],{"class":77,"line":345},10,[75,347,348],{"class":223},"  username: ",[75,350,351],{"class":85},"\"Support Bot\"",[75,353,354],{"class":223},",\n",[75,356,358,361,364],{"class":77,"line":357},11,[75,359,360],{"class":223},"  avatar: ",[75,362,363],{"class":85},"\"/bot.png\"",[75,365,354],{"class":223},[75,367,369],{"class":77,"line":368},12,[75,370,371],{"class":223},"});\n",[75,373,375,377,380,382,384,387],{"class":77,"line":374},13,[75,376,283],{"class":219},[75,378,379],{"class":286}," agents",[75,381,290],{"class":219},[75,383,315],{"class":219},[75,385,386],{"class":81}," NoLagAgents",[75,388,321],{"class":223},[75,390,392],{"class":77,"line":391},14,[75,393,327],{"class":223},[75,395,397,399,402,404],{"class":77,"line":396},15,[75,398,333],{"class":223},[75,400,401],{"class":286},"AGENTS_APP_SLUG",[75,403,339],{"class":223},[75,405,406],{"class":276},"// the suffixed slug of the agents app\n",[75,408,410,413,416],{"class":77,"line":409},16,[75,411,412],{"class":223},"  agentId: ",[75,414,415],{"class":85},"\"bridge\"",[75,417,354],{"class":223},[75,419,421,424,426,429,432],{"class":77,"line":420},17,[75,422,423],{"class":223},"  presence: { name: ",[75,425,415],{"class":85},[75,427,428],{"class":223},", role: ",[75,430,431],{"class":85},"\"orchestrator\"",[75,433,434],{"class":223}," },\n",[75,436,438],{"class":77,"line":437},18,[75,439,371],{"class":223},[75,441,443],{"class":77,"line":442},19,[75,444,270],{"emptyLinePlaceholder":269},[75,446,448,451,454,457],{"class":77,"line":447},20,[75,449,450],{"class":219},"await",[75,452,453],{"class":223}," client.",[75,455,456],{"class":81},"connect",[75,458,459],{"class":223},"();\n",[75,461,463,465,468,471,474,477,480,483,485],{"class":77,"line":462},21,[75,464,450],{"class":219},[75,466,467],{"class":286}," Promise",[75,469,470],{"class":223},".",[75,472,473],{"class":81},"all",[75,475,476],{"class":223},"([chat.",[75,478,479],{"class":81},"ready",[75,481,482],{"class":223},"(), agents.",[75,484,479],{"class":81},[75,486,487],{"class":223},"()]);\n",[75,489,491],{"class":77,"line":490},22,[75,492,270],{"emptyLinePlaceholder":269},[75,494,496,498,501,503,506,509,511,514],{"class":77,"line":495},23,[75,497,283],{"class":219},[75,499,500],{"class":286}," chatRoom",[75,502,290],{"class":219},[75,504,505],{"class":223}," chat.",[75,507,508],{"class":81},"joinRoom",[75,510,296],{"class":223},[75,512,513],{"class":85},"\"support\"",[75,515,302],{"class":223},[75,517,519,521,524,526,529,532,534,537],{"class":77,"line":518},24,[75,520,283],{"class":219},[75,522,523],{"class":286}," agentRoom",[75,525,290],{"class":219},[75,527,528],{"class":223}," agents.",[75,530,531],{"class":81},"room",[75,533,296],{"class":223},[75,535,536],{"class":85},"\"support-workflow\"",[75,538,302],{"class":223},[75,540,542,544,547,549,551,554],{"class":77,"line":541},25,[75,543,283],{"class":219},[75,545,546],{"class":286}," handoff",[75,548,290],{"class":219},[75,550,315],{"class":219},[75,552,553],{"class":81}," Handoff",[75,555,556],{"class":223},"(agentRoom);\n",[75,558,560,562,565,567,570,573],{"class":77,"line":559},26,[75,561,283],{"class":219},[75,563,564],{"class":286}," botId",[75,566,290],{"class":219},[75,568,569],{"class":223}," chat.localUser",[75,571,572],{"class":219},"!",[75,574,575],{"class":223},".userId;\n",[75,577,579],{"class":77,"line":578},27,[75,580,270],{"emptyLinePlaceholder":269},[75,582,584],{"class":77,"line":583},28,[75,585,586],{"class":276},"// Forward user messages to the agent workflow. Publishers never receive\n",[75,588,590],{"class":77,"line":589},29,[75,591,592],{"class":276},"// their own messages, so the bot's replies never come back through here.\n",[75,594,596,599,602,604,607,609,612,615,619,622,625],{"class":77,"line":595},30,[75,597,598],{"class":223},"chatRoom.",[75,600,601],{"class":81},"on",[75,603,296],{"class":223},[75,605,606],{"class":85},"\"message\"",[75,608,339],{"class":223},[75,610,611],{"class":219},"async",[75,613,614],{"class":223}," (",[75,616,618],{"class":617},"s4XuR","msg",[75,620,621],{"class":223},") ",[75,623,624],{"class":219},"=>",[75,626,627],{"class":223}," {\n",[75,629,631],{"class":77,"line":630},31,[75,632,633],{"class":276},"  // Show typing indicator while the worker thinks\n",[75,635,637,640,643],{"class":77,"line":636},32,[75,638,639],{"class":223},"  chatRoom.",[75,641,642],{"class":81},"startTyping",[75,644,459],{"class":223},[75,646,648],{"class":77,"line":647},33,[75,649,270],{"emptyLinePlaceholder":269},[75,651,653,656,659,661,664,667,670,673,675,678,681,684,687,690,693,695,697],{"class":77,"line":652},34,[75,654,655],{"class":219},"  const",[75,657,658],{"class":286}," history",[75,660,290],{"class":219},[75,662,663],{"class":223}," chatRoom.",[75,665,666],{"class":81},"getMessages",[75,668,669],{"class":223},"().",[75,671,672],{"class":81},"slice",[75,674,296],{"class":223},[75,676,677],{"class":219},"-",[75,679,680],{"class":286},"10",[75,682,683],{"class":223},").",[75,685,686],{"class":81},"map",[75,688,689],{"class":223},"((",[75,691,692],{"class":617},"m",[75,694,621],{"class":223},[75,696,624],{"class":219},[75,698,699],{"class":223}," ({\n",[75,701,703,706,709,712,715,718,721,724],{"class":77,"line":702},35,[75,704,705],{"class":223},"    role: m.userId ",[75,707,708],{"class":219},"===",[75,710,711],{"class":223}," botId ",[75,713,714],{"class":219},"?",[75,716,717],{"class":85}," \"assistant\"",[75,719,720],{"class":219}," :",[75,722,723],{"class":85}," \"user\"",[75,725,354],{"class":223},[75,727,729],{"class":77,"line":728},36,[75,730,731],{"class":223},"    content: m.text,\n",[75,733,735],{"class":77,"line":734},37,[75,736,737],{"class":223},"  }));\n",[75,739,741],{"class":77,"line":740},38,[75,742,270],{"emptyLinePlaceholder":269},[75,744,746,748,751,753,756,759,762],{"class":77,"line":745},39,[75,747,655],{"class":219},[75,749,750],{"class":286}," result",[75,752,290],{"class":219},[75,754,755],{"class":219}," await",[75,757,758],{"class":223}," handoff.",[75,760,761],{"class":81},"dispatch",[75,763,764],{"class":223},"(\n",[75,766,768,771],{"class":77,"line":767},40,[75,769,770],{"class":85},"    \"customer-support\"",[75,772,354],{"class":223},[75,774,776],{"class":77,"line":775},41,[75,777,778],{"class":223},"    { userMessage: msg.text, userId: msg.userId, history },\n",[75,780,782,785,788,791,794],{"class":77,"line":781},42,[75,783,784],{"class":223},"    { waitForResult: ",[75,786,787],{"class":286},"true",[75,789,790],{"class":223},", timeout: ",[75,792,793],{"class":286},"30000",[75,795,434],{"class":223},[75,797,799],{"class":77,"line":798},43,[75,800,801],{"class":223},"  );\n",[75,803,805],{"class":77,"line":804},44,[75,806,270],{"emptyLinePlaceholder":269},[75,808,810,812,815],{"class":77,"line":809},45,[75,811,639],{"class":223},[75,813,814],{"class":81},"stopTyping",[75,816,459],{"class":223},[75,818,820,823,826,829,832,834,837],{"class":77,"line":819},46,[75,821,822],{"class":219},"  if",[75,824,825],{"class":223}," (result ",[75,827,828],{"class":219},"&&",[75,830,831],{"class":223}," result.status ",[75,833,708],{"class":219},[75,835,836],{"class":85}," \"success\"",[75,838,839],{"class":223},") {\n",[75,841,843,846,849,851,854],{"class":77,"line":842},47,[75,844,845],{"class":223},"    chatRoom.",[75,847,848],{"class":81},"sendMessage",[75,850,296],{"class":223},[75,852,853],{"class":81},"String",[75,855,856],{"class":223},"(result.payload.response));\n",[75,858,860],{"class":77,"line":859},48,[75,861,862],{"class":223},"  }\n",[75,864,866],{"class":77,"line":865},49,[75,867,371],{"class":223},[11,869,870,871,874,875,878,879,882,883,470],{},"The bridge shows typing indicators while the agent is processing, giving users a natural chat experience. The last ten messages are passed to the agent as ",[15,872,873],{},"role","/",[15,876,877],{},"content"," pairs so it has conversation context. ",[15,880,881],{},"dispatch()"," checks room presence before publishing and throws if no connected worker advertises the capability, so start a worker before the bridge or pass ",[15,884,885],{},"allowNoWorkers: true",[24,887,889],{"id":888},"step-4-build-the-ai-worker","Step 4: Build the AI Worker",[11,891,892],{},"Workers advertise their capabilities through presence and process incoming tasks. You can use any LLM - OpenAI, Anthropic, a local model, or a custom pipeline:",[62,894,895],{},[65,896,899],{"className":209,"code":897,"filename":898,"language":212,"meta":71,"style":71},"import { NoLag } from \"@nolag/js-sdk\";\nimport { NoLagAgents, Handoff, Observe } from \"@nolag/agents\";\n\n// loadBalance: with several workers in the \"support-workers\" group, each task\n// goes to one of them. Without it every worker answers every task.\nconst client = NoLag(WORKER_TOKEN, { loadBalance: true, loadBalanceGroup: \"support-workers\" });\nconst agents = new NoLagAgents({\n  client,\n  appName: AGENTS_APP_SLUG,\n  agentId: \"support-worker-1\",\n  presence: { name: \"support-worker-1\", role: \"agent\", capabilities: [\"customer-support\"] },\n});\nawait client.connect();\nawait agents.ready();\n\nconst room = agents.room(\"support-workflow\");\nconst handoff = new Handoff(room);\nconst observe = new Observe(room, agents.agentId);\n\ntype Turn = { role: \"user\" | \"assistant\"; content: string };\n\nhandoff.onTask([\"customer-support\"], async (task, respond) => {\n  const userMessage = String(task.payload.userMessage);\n  const history = (task.payload.history ?? []) as Turn[];\n  const started = Date.now();\n\n  try {\n    // Call your LLM of choice\n    const completion = await openai.chat.completions.create({\n      model: \"gpt-4o\",\n      messages: [\n        { role: \"system\", content: \"You are a helpful customer support agent.\" },\n        ...history,\n        { role: \"user\", content: userMessage },\n      ],\n    });\n\n    respond(\"success\", { response: completion.choices[0].message.content });\n    observe.emit(\"task:completed\", { taskId: task.taskId, durationMs: Date.now() - started });\n  } catch (err) {\n    respond(\"error\", {}, { code: \"llm_failed\", message: String(err) });\n    observe.emit(\"task:failed\", { taskId: task.taskId, error: String(err) }, \"error\");\n  }\n});\n","worker.ts",[15,900,901,913,926,930,935,940,969,983,987,995,1004,1024,1028,1038,1048,1052,1071,1086,1103,1107,1145,1149,1183,1198,1223,1240,1244,1251,1256,1276,1286,1291,1307,1315,1325,1330,1335,1339,1358,1384,1395,1418,1441,1445],{"__ignoreMap":71},[75,902,903,905,907,909,911],{"class":77,"line":78},[75,904,220],{"class":219},[75,906,224],{"class":223},[75,908,227],{"class":219},[75,910,230],{"class":85},[75,912,233],{"class":223},[75,914,915,917,920,922,924],{"class":77,"line":236},[75,916,220],{"class":219},[75,918,919],{"class":223}," { NoLagAgents, Handoff, Observe } ",[75,921,227],{"class":219},[75,923,261],{"class":85},[75,925,233],{"class":223},[75,927,928],{"class":77,"line":251},[75,929,270],{"emptyLinePlaceholder":269},[75,931,932],{"class":77,"line":266},[75,933,934],{"class":276},"// loadBalance: with several workers in the \"support-workers\" group, each task\n",[75,936,937],{"class":77,"line":273},[75,938,939],{"class":276},"// goes to one of them. Without it every worker answers every task.\n",[75,941,942,944,946,948,950,952,955,958,960,963,966],{"class":77,"line":280},[75,943,283],{"class":219},[75,945,287],{"class":286},[75,947,290],{"class":219},[75,949,293],{"class":81},[75,951,296],{"class":223},[75,953,954],{"class":286},"WORKER_TOKEN",[75,956,957],{"class":223},", { loadBalance: ",[75,959,787],{"class":286},[75,961,962],{"class":223},", loadBalanceGroup: ",[75,964,965],{"class":85},"\"support-workers\"",[75,967,968],{"class":223}," });\n",[75,970,971,973,975,977,979,981],{"class":77,"line":305},[75,972,283],{"class":219},[75,974,379],{"class":286},[75,976,290],{"class":219},[75,978,315],{"class":219},[75,980,386],{"class":81},[75,982,321],{"class":223},[75,984,985],{"class":77,"line":324},[75,986,327],{"class":223},[75,988,989,991,993],{"class":77,"line":330},[75,990,333],{"class":223},[75,992,401],{"class":286},[75,994,354],{"class":223},[75,996,997,999,1002],{"class":77,"line":345},[75,998,412],{"class":223},[75,1000,1001],{"class":85},"\"support-worker-1\"",[75,1003,354],{"class":223},[75,1005,1006,1008,1010,1012,1015,1018,1021],{"class":77,"line":357},[75,1007,423],{"class":223},[75,1009,1001],{"class":85},[75,1011,428],{"class":223},[75,1013,1014],{"class":85},"\"agent\"",[75,1016,1017],{"class":223},", capabilities: [",[75,1019,1020],{"class":85},"\"customer-support\"",[75,1022,1023],{"class":223},"] },\n",[75,1025,1026],{"class":77,"line":368},[75,1027,371],{"class":223},[75,1029,1030,1032,1034,1036],{"class":77,"line":374},[75,1031,450],{"class":219},[75,1033,453],{"class":223},[75,1035,456],{"class":81},[75,1037,459],{"class":223},[75,1039,1040,1042,1044,1046],{"class":77,"line":391},[75,1041,450],{"class":219},[75,1043,528],{"class":223},[75,1045,479],{"class":81},[75,1047,459],{"class":223},[75,1049,1050],{"class":77,"line":396},[75,1051,270],{"emptyLinePlaceholder":269},[75,1053,1054,1056,1059,1061,1063,1065,1067,1069],{"class":77,"line":409},[75,1055,283],{"class":219},[75,1057,1058],{"class":286}," room",[75,1060,290],{"class":219},[75,1062,528],{"class":223},[75,1064,531],{"class":81},[75,1066,296],{"class":223},[75,1068,536],{"class":85},[75,1070,302],{"class":223},[75,1072,1073,1075,1077,1079,1081,1083],{"class":77,"line":420},[75,1074,283],{"class":219},[75,1076,546],{"class":286},[75,1078,290],{"class":219},[75,1080,315],{"class":219},[75,1082,553],{"class":81},[75,1084,1085],{"class":223},"(room);\n",[75,1087,1088,1090,1093,1095,1097,1100],{"class":77,"line":437},[75,1089,283],{"class":219},[75,1091,1092],{"class":286}," observe",[75,1094,290],{"class":219},[75,1096,315],{"class":219},[75,1098,1099],{"class":81}," Observe",[75,1101,1102],{"class":223},"(room, agents.agentId);\n",[75,1104,1105],{"class":77,"line":442},[75,1106,270],{"emptyLinePlaceholder":269},[75,1108,1109,1112,1115,1117,1120,1122,1125,1127,1130,1132,1135,1137,1139,1142],{"class":77,"line":447},[75,1110,1111],{"class":219},"type",[75,1113,1114],{"class":81}," Turn",[75,1116,290],{"class":219},[75,1118,1119],{"class":223}," { ",[75,1121,873],{"class":617},[75,1123,1124],{"class":219},":",[75,1126,723],{"class":85},[75,1128,1129],{"class":219}," |",[75,1131,717],{"class":85},[75,1133,1134],{"class":223},"; ",[75,1136,877],{"class":617},[75,1138,1124],{"class":219},[75,1140,1141],{"class":286}," string",[75,1143,1144],{"class":223}," };\n",[75,1146,1147],{"class":77,"line":462},[75,1148,270],{"emptyLinePlaceholder":269},[75,1150,1151,1154,1157,1160,1162,1165,1167,1169,1172,1174,1177,1179,1181],{"class":77,"line":490},[75,1152,1153],{"class":223},"handoff.",[75,1155,1156],{"class":81},"onTask",[75,1158,1159],{"class":223},"([",[75,1161,1020],{"class":85},[75,1163,1164],{"class":223},"], ",[75,1166,611],{"class":219},[75,1168,614],{"class":223},[75,1170,1171],{"class":617},"task",[75,1173,339],{"class":223},[75,1175,1176],{"class":617},"respond",[75,1178,621],{"class":223},[75,1180,624],{"class":219},[75,1182,627],{"class":223},[75,1184,1185,1187,1190,1192,1195],{"class":77,"line":495},[75,1186,655],{"class":219},[75,1188,1189],{"class":286}," userMessage",[75,1191,290],{"class":219},[75,1193,1194],{"class":81}," String",[75,1196,1197],{"class":223},"(task.payload.userMessage);\n",[75,1199,1200,1202,1204,1206,1209,1212,1215,1218,1220],{"class":77,"line":518},[75,1201,655],{"class":219},[75,1203,658],{"class":286},[75,1205,290],{"class":219},[75,1207,1208],{"class":223}," (task.payload.history ",[75,1210,1211],{"class":219},"??",[75,1213,1214],{"class":223}," []) ",[75,1216,1217],{"class":219},"as",[75,1219,1114],{"class":81},[75,1221,1222],{"class":223},"[];\n",[75,1224,1225,1227,1230,1232,1235,1238],{"class":77,"line":541},[75,1226,655],{"class":219},[75,1228,1229],{"class":286}," started",[75,1231,290],{"class":219},[75,1233,1234],{"class":223}," Date.",[75,1236,1237],{"class":81},"now",[75,1239,459],{"class":223},[75,1241,1242],{"class":77,"line":559},[75,1243,270],{"emptyLinePlaceholder":269},[75,1245,1246,1249],{"class":77,"line":578},[75,1247,1248],{"class":219},"  try",[75,1250,627],{"class":223},[75,1252,1253],{"class":77,"line":583},[75,1254,1255],{"class":276},"    // Call your LLM of choice\n",[75,1257,1258,1261,1264,1266,1268,1271,1274],{"class":77,"line":589},[75,1259,1260],{"class":219},"    const",[75,1262,1263],{"class":286}," completion",[75,1265,290],{"class":219},[75,1267,755],{"class":219},[75,1269,1270],{"class":223}," openai.chat.completions.",[75,1272,1273],{"class":81},"create",[75,1275,321],{"class":223},[75,1277,1278,1281,1284],{"class":77,"line":595},[75,1279,1280],{"class":223},"      model: ",[75,1282,1283],{"class":85},"\"gpt-4o\"",[75,1285,354],{"class":223},[75,1287,1288],{"class":77,"line":630},[75,1289,1290],{"class":223},"      messages: [\n",[75,1292,1293,1296,1299,1302,1305],{"class":77,"line":636},[75,1294,1295],{"class":223},"        { role: ",[75,1297,1298],{"class":85},"\"system\"",[75,1300,1301],{"class":223},", content: ",[75,1303,1304],{"class":85},"\"You are a helpful customer support agent.\"",[75,1306,434],{"class":223},[75,1308,1309,1312],{"class":77,"line":647},[75,1310,1311],{"class":219},"        ...",[75,1313,1314],{"class":223},"history,\n",[75,1316,1317,1319,1322],{"class":77,"line":652},[75,1318,1295],{"class":223},[75,1320,1321],{"class":85},"\"user\"",[75,1323,1324],{"class":223},", content: userMessage },\n",[75,1326,1327],{"class":77,"line":702},[75,1328,1329],{"class":223},"      ],\n",[75,1331,1332],{"class":77,"line":728},[75,1333,1334],{"class":223},"    });\n",[75,1336,1337],{"class":77,"line":734},[75,1338,270],{"emptyLinePlaceholder":269},[75,1340,1341,1344,1346,1349,1352,1355],{"class":77,"line":740},[75,1342,1343],{"class":81},"    respond",[75,1345,296],{"class":223},[75,1347,1348],{"class":85},"\"success\"",[75,1350,1351],{"class":223},", { response: completion.choices[",[75,1353,1354],{"class":286},"0",[75,1356,1357],{"class":223},"].message.content });\n",[75,1359,1360,1363,1366,1368,1371,1374,1376,1379,1381],{"class":77,"line":745},[75,1361,1362],{"class":223},"    observe.",[75,1364,1365],{"class":81},"emit",[75,1367,296],{"class":223},[75,1369,1370],{"class":85},"\"task:completed\"",[75,1372,1373],{"class":223},", { taskId: task.taskId, durationMs: Date.",[75,1375,1237],{"class":81},[75,1377,1378],{"class":223},"() ",[75,1380,677],{"class":219},[75,1382,1383],{"class":223}," started });\n",[75,1385,1386,1389,1392],{"class":77,"line":767},[75,1387,1388],{"class":223},"  } ",[75,1390,1391],{"class":219},"catch",[75,1393,1394],{"class":223}," (err) {\n",[75,1396,1397,1399,1401,1404,1407,1410,1413,1415],{"class":77,"line":775},[75,1398,1343],{"class":81},[75,1400,296],{"class":223},[75,1402,1403],{"class":85},"\"error\"",[75,1405,1406],{"class":223},", {}, { code: ",[75,1408,1409],{"class":85},"\"llm_failed\"",[75,1411,1412],{"class":223},", message: ",[75,1414,853],{"class":81},[75,1416,1417],{"class":223},"(err) });\n",[75,1419,1420,1422,1424,1426,1429,1432,1434,1437,1439],{"class":77,"line":781},[75,1421,1362],{"class":223},[75,1423,1365],{"class":81},[75,1425,296],{"class":223},[75,1427,1428],{"class":85},"\"task:failed\"",[75,1430,1431],{"class":223},", { taskId: task.taskId, error: ",[75,1433,853],{"class":81},[75,1435,1436],{"class":223},"(err) }, ",[75,1438,1403],{"class":85},[75,1440,302],{"class":223},[75,1442,1443],{"class":77,"line":798},[75,1444,862],{"class":223},[75,1446,1447],{"class":77,"line":804},[75,1448,371],{"class":223},[11,1450,1451,1452,1455],{},"Workers are stateless and horizontally scalable. Run more instances with the same ",[15,1453,1454],{},"loadBalanceGroup"," and the broker hands each task to one member of the group. Load balancing is opt-in on the core client; leave it off and every instance receives, and answers, every task.",[24,1457,1459],{"id":1458},"step-5-add-escalation","Step 5: Add Escalation",[11,1461,1462],{},"Use the Approve pattern to escalate sensitive conversations to a human. The worker detects negative sentiment and requests approval before responding:",[62,1464,1465,1794],{},[65,1466,1469],{"className":209,"code":1467,"filename":1468,"language":212,"meta":71,"style":71},"import { Approve } from \"@nolag/agents\";\n\n// In the worker, detect when to escalate\nconst approve = new Approve(room, agents.agentId);\n\nhandoff.onTask([\"customer-support\"], async (task, respond) => {\n  const userMessage = String(task.payload.userMessage);\n  const sentiment = await analyzeSentiment(userMessage);\n\n  if (sentiment.score \u003C -0.7) {\n    const suggestedResponse = await generateResponse(task.payload);\n\n    // Escalate: a human sees the request in an onRequest() handler and\n    // answers approved, rejected, or deferred\n    const approval = await approve.request(\n      \"escalate-to-human\",\n      { userMessage, suggestedResponse },\n      { urgency: \"high\", timeout: 5 * 60 * 1000 },\n    );\n\n    if (approval.decision === \"approved\") {\n      respond(\"success\", { response: suggestedResponse });\n    } else {\n      respond(\"success\", { response: \"A member of our team will follow up with you shortly.\" });\n    }\n  } else {\n    // Normal LLM response\n    const response = await generateResponse(task.payload);\n    respond(\"success\", { response });\n  }\n});\n","escalation.ts",[15,1470,1471,1484,1488,1493,1509,1513,1541,1553,1570,1574,1592,1609,1613,1618,1623,1642,1649,1654,1680,1685,1689,1704,1716,1726,1742,1747,1755,1760,1775,1786,1790],{"__ignoreMap":71},[75,1472,1473,1475,1478,1480,1482],{"class":77,"line":78},[75,1474,220],{"class":219},[75,1476,1477],{"class":223}," { Approve } ",[75,1479,227],{"class":219},[75,1481,261],{"class":85},[75,1483,233],{"class":223},[75,1485,1486],{"class":77,"line":236},[75,1487,270],{"emptyLinePlaceholder":269},[75,1489,1490],{"class":77,"line":251},[75,1491,1492],{"class":276},"// In the worker, detect when to escalate\n",[75,1494,1495,1497,1500,1502,1504,1507],{"class":77,"line":266},[75,1496,283],{"class":219},[75,1498,1499],{"class":286}," approve",[75,1501,290],{"class":219},[75,1503,315],{"class":219},[75,1505,1506],{"class":81}," Approve",[75,1508,1102],{"class":223},[75,1510,1511],{"class":77,"line":273},[75,1512,270],{"emptyLinePlaceholder":269},[75,1514,1515,1517,1519,1521,1523,1525,1527,1529,1531,1533,1535,1537,1539],{"class":77,"line":280},[75,1516,1153],{"class":223},[75,1518,1156],{"class":81},[75,1520,1159],{"class":223},[75,1522,1020],{"class":85},[75,1524,1164],{"class":223},[75,1526,611],{"class":219},[75,1528,614],{"class":223},[75,1530,1171],{"class":617},[75,1532,339],{"class":223},[75,1534,1176],{"class":617},[75,1536,621],{"class":223},[75,1538,624],{"class":219},[75,1540,627],{"class":223},[75,1542,1543,1545,1547,1549,1551],{"class":77,"line":305},[75,1544,655],{"class":219},[75,1546,1189],{"class":286},[75,1548,290],{"class":219},[75,1550,1194],{"class":81},[75,1552,1197],{"class":223},[75,1554,1555,1557,1560,1562,1564,1567],{"class":77,"line":324},[75,1556,655],{"class":219},[75,1558,1559],{"class":286}," sentiment",[75,1561,290],{"class":219},[75,1563,755],{"class":219},[75,1565,1566],{"class":81}," analyzeSentiment",[75,1568,1569],{"class":223},"(userMessage);\n",[75,1571,1572],{"class":77,"line":330},[75,1573,270],{"emptyLinePlaceholder":269},[75,1575,1576,1578,1581,1584,1587,1590],{"class":77,"line":345},[75,1577,822],{"class":219},[75,1579,1580],{"class":223}," (sentiment.score ",[75,1582,1583],{"class":219},"\u003C",[75,1585,1586],{"class":219}," -",[75,1588,1589],{"class":286},"0.7",[75,1591,839],{"class":223},[75,1593,1594,1596,1599,1601,1603,1606],{"class":77,"line":357},[75,1595,1260],{"class":219},[75,1597,1598],{"class":286}," suggestedResponse",[75,1600,290],{"class":219},[75,1602,755],{"class":219},[75,1604,1605],{"class":81}," generateResponse",[75,1607,1608],{"class":223},"(task.payload);\n",[75,1610,1611],{"class":77,"line":368},[75,1612,270],{"emptyLinePlaceholder":269},[75,1614,1615],{"class":77,"line":374},[75,1616,1617],{"class":276},"    // Escalate: a human sees the request in an onRequest() handler and\n",[75,1619,1620],{"class":77,"line":391},[75,1621,1622],{"class":276},"    // answers approved, rejected, or deferred\n",[75,1624,1625,1627,1630,1632,1634,1637,1640],{"class":77,"line":396},[75,1626,1260],{"class":219},[75,1628,1629],{"class":286}," approval",[75,1631,290],{"class":219},[75,1633,755],{"class":219},[75,1635,1636],{"class":223}," approve.",[75,1638,1639],{"class":81},"request",[75,1641,764],{"class":223},[75,1643,1644,1647],{"class":77,"line":409},[75,1645,1646],{"class":85},"      \"escalate-to-human\"",[75,1648,354],{"class":223},[75,1650,1651],{"class":77,"line":420},[75,1652,1653],{"class":223},"      { userMessage, suggestedResponse },\n",[75,1655,1656,1659,1662,1664,1667,1670,1673,1675,1678],{"class":77,"line":437},[75,1657,1658],{"class":223},"      { urgency: ",[75,1660,1661],{"class":85},"\"high\"",[75,1663,790],{"class":223},[75,1665,1666],{"class":286},"5",[75,1668,1669],{"class":219}," *",[75,1671,1672],{"class":286}," 60",[75,1674,1669],{"class":219},[75,1676,1677],{"class":286}," 1000",[75,1679,434],{"class":223},[75,1681,1682],{"class":77,"line":442},[75,1683,1684],{"class":223},"    );\n",[75,1686,1687],{"class":77,"line":447},[75,1688,270],{"emptyLinePlaceholder":269},[75,1690,1691,1694,1697,1699,1702],{"class":77,"line":462},[75,1692,1693],{"class":219},"    if",[75,1695,1696],{"class":223}," (approval.decision ",[75,1698,708],{"class":219},[75,1700,1701],{"class":85}," \"approved\"",[75,1703,839],{"class":223},[75,1705,1706,1709,1711,1713],{"class":77,"line":490},[75,1707,1708],{"class":81},"      respond",[75,1710,296],{"class":223},[75,1712,1348],{"class":85},[75,1714,1715],{"class":223},", { response: suggestedResponse });\n",[75,1717,1718,1721,1724],{"class":77,"line":495},[75,1719,1720],{"class":223},"    } ",[75,1722,1723],{"class":219},"else",[75,1725,627],{"class":223},[75,1727,1728,1730,1732,1734,1737,1740],{"class":77,"line":518},[75,1729,1708],{"class":81},[75,1731,296],{"class":223},[75,1733,1348],{"class":85},[75,1735,1736],{"class":223},", { response: ",[75,1738,1739],{"class":85},"\"A member of our team will follow up with you shortly.\"",[75,1741,968],{"class":223},[75,1743,1744],{"class":77,"line":541},[75,1745,1746],{"class":223},"    }\n",[75,1748,1749,1751,1753],{"class":77,"line":559},[75,1750,1388],{"class":223},[75,1752,1723],{"class":219},[75,1754,627],{"class":223},[75,1756,1757],{"class":77,"line":578},[75,1758,1759],{"class":276},"    // Normal LLM response\n",[75,1761,1762,1764,1767,1769,1771,1773],{"class":77,"line":583},[75,1763,1260],{"class":219},[75,1765,1766],{"class":286}," response",[75,1768,290],{"class":219},[75,1770,755],{"class":219},[75,1772,1605],{"class":81},[75,1774,1608],{"class":223},[75,1776,1777,1779,1781,1783],{"class":77,"line":589},[75,1778,1343],{"class":81},[75,1780,296],{"class":223},[75,1782,1348],{"class":85},[75,1784,1785],{"class":223},", { response });\n",[75,1787,1788],{"class":77,"line":595},[75,1789,862],{"class":223},[75,1791,1792],{"class":77,"line":630},[75,1793,371],{"class":223},[65,1795,1798],{"className":209,"code":1796,"filename":1797,"language":212,"meta":71,"style":71},"import { Approve } from \"@nolag/agents\";\n\n// On the human reviewer's side (a dashboard or CLI in the same room)\nconst approve = new Approve(room, agents.agentId);\n\napprove.onRequest((request, respond) => {\n  console.log(\"Review needed:\", request.action, request.context);\n  respond(\"approved\"); // or \"rejected\" / \"deferred\", with an optional reason\n});\n","reviewer.ts",[15,1799,1800,1812,1816,1821,1835,1839,1861,1877,1893],{"__ignoreMap":71},[75,1801,1802,1804,1806,1808,1810],{"class":77,"line":78},[75,1803,220],{"class":219},[75,1805,1477],{"class":223},[75,1807,227],{"class":219},[75,1809,261],{"class":85},[75,1811,233],{"class":223},[75,1813,1814],{"class":77,"line":236},[75,1815,270],{"emptyLinePlaceholder":269},[75,1817,1818],{"class":77,"line":251},[75,1819,1820],{"class":276},"// On the human reviewer's side (a dashboard or CLI in the same room)\n",[75,1822,1823,1825,1827,1829,1831,1833],{"class":77,"line":266},[75,1824,283],{"class":219},[75,1826,1499],{"class":286},[75,1828,290],{"class":219},[75,1830,315],{"class":219},[75,1832,1506],{"class":81},[75,1834,1102],{"class":223},[75,1836,1837],{"class":77,"line":273},[75,1838,270],{"emptyLinePlaceholder":269},[75,1840,1841,1844,1847,1849,1851,1853,1855,1857,1859],{"class":77,"line":280},[75,1842,1843],{"class":223},"approve.",[75,1845,1846],{"class":81},"onRequest",[75,1848,689],{"class":223},[75,1850,1639],{"class":617},[75,1852,339],{"class":223},[75,1854,1176],{"class":617},[75,1856,621],{"class":223},[75,1858,624],{"class":219},[75,1860,627],{"class":223},[75,1862,1863,1866,1869,1871,1874],{"class":77,"line":305},[75,1864,1865],{"class":223},"  console.",[75,1867,1868],{"class":81},"log",[75,1870,296],{"class":223},[75,1872,1873],{"class":85},"\"Review needed:\"",[75,1875,1876],{"class":223},", request.action, request.context);\n",[75,1878,1879,1882,1884,1887,1890],{"class":77,"line":324},[75,1880,1881],{"class":81},"  respond",[75,1883,296],{"class":223},[75,1885,1886],{"class":85},"\"approved\"",[75,1888,1889],{"class":223},"); ",[75,1891,1892],{"class":276},"// or \"rejected\" / \"deferred\", with an optional reason\n",[75,1894,1895],{"class":77,"line":330},[75,1896,371],{"class":223},[11,1898,1899],{},"The approval topic is published retained, so the most recent approval message is delivered to a reviewer who joins the room later.",[24,1901,1903],{"id":1902},"step-6-monitor-everything","Step 6: Monitor Everything",[11,1905,1906,1907,153,1910,1913],{},"The Observe pattern gives you visibility into the chatbot's performance. Handoff does not emit events on its own; the worker in Step 4 emits ",[15,1908,1909],{},"task:completed",[15,1911,1912],{},"task:failed",", and any observer in the room can consume them:",[62,1915,1916],{},[65,1917,1920],{"className":209,"code":1918,"filename":1919,"language":212,"meta":71,"style":71},"import { NoLag } from \"@nolag/js-sdk\";\nimport { NoLagAgents, Observe } from \"@nolag/agents\";\n\nconst client = NoLag(MONITOR_TOKEN);\nconst agents = new NoLagAgents({\n  client,\n  appName: AGENTS_APP_SLUG,\n  agentId: \"monitor\",\n  presence: { name: \"monitor\", role: \"observer\" },\n});\nawait client.connect();\nawait agents.ready();\n\nconst room = agents.room(\"support-workflow\");\nconst observe = new Observe(room, agents.agentId);\n\n// Track metrics\nlet totalQueries = 0;\nlet avgResponseTime = 0;\n\nobserve.on((event) => {\n  totalQueries++;\n  const duration = Number(event.payload.durationMs);\n  avgResponseTime = (avgResponseTime * (totalQueries - 1) + duration) / totalQueries;\n  console.log(`Avg response: ${avgResponseTime}ms | Total: ${totalQueries}`);\n}, { category: \"task:completed\" });\n\nobserve.on((event) => {\n  console.error(\"Agent failed:\", event.payload.error, \"from\", event.emittedBy);\n}, { category: \"task:failed\" });\n\n// Monitor agent health\nconst workers = room.findAgents(\"customer-support\");\nconsole.log(\"Workers online:\", workers.length);\n\nroom.on(\"presenceLeave\", (actorId) => {\n  console.warn(\"Agent left:\", actorId, \"remaining:\", room.getConnectedAgents().length);\n});\n","monitor.ts",[15,1921,1922,1934,1947,1951,1968,1982,1986,1994,2003,2016,2020,2030,2040,2044,2062,2076,2080,2085,2101,2114,2118,2136,2146,2161,2195,2220,2229,2233,2249,2270,2278,2282,2287,2308,2328,2332,2356,2386],{"__ignoreMap":71},[75,1923,1924,1926,1928,1930,1932],{"class":77,"line":78},[75,1925,220],{"class":219},[75,1927,224],{"class":223},[75,1929,227],{"class":219},[75,1931,230],{"class":85},[75,1933,233],{"class":223},[75,1935,1936,1938,1941,1943,1945],{"class":77,"line":236},[75,1937,220],{"class":219},[75,1939,1940],{"class":223}," { NoLagAgents, Observe } ",[75,1942,227],{"class":219},[75,1944,261],{"class":85},[75,1946,233],{"class":223},[75,1948,1949],{"class":77,"line":251},[75,1950,270],{"emptyLinePlaceholder":269},[75,1952,1953,1955,1957,1959,1961,1963,1966],{"class":77,"line":266},[75,1954,283],{"class":219},[75,1956,287],{"class":286},[75,1958,290],{"class":219},[75,1960,293],{"class":81},[75,1962,296],{"class":223},[75,1964,1965],{"class":286},"MONITOR_TOKEN",[75,1967,302],{"class":223},[75,1969,1970,1972,1974,1976,1978,1980],{"class":77,"line":273},[75,1971,283],{"class":219},[75,1973,379],{"class":286},[75,1975,290],{"class":219},[75,1977,315],{"class":219},[75,1979,386],{"class":81},[75,1981,321],{"class":223},[75,1983,1984],{"class":77,"line":280},[75,1985,327],{"class":223},[75,1987,1988,1990,1992],{"class":77,"line":305},[75,1989,333],{"class":223},[75,1991,401],{"class":286},[75,1993,354],{"class":223},[75,1995,1996,1998,2001],{"class":77,"line":324},[75,1997,412],{"class":223},[75,1999,2000],{"class":85},"\"monitor\"",[75,2002,354],{"class":223},[75,2004,2005,2007,2009,2011,2014],{"class":77,"line":330},[75,2006,423],{"class":223},[75,2008,2000],{"class":85},[75,2010,428],{"class":223},[75,2012,2013],{"class":85},"\"observer\"",[75,2015,434],{"class":223},[75,2017,2018],{"class":77,"line":345},[75,2019,371],{"class":223},[75,2021,2022,2024,2026,2028],{"class":77,"line":357},[75,2023,450],{"class":219},[75,2025,453],{"class":223},[75,2027,456],{"class":81},[75,2029,459],{"class":223},[75,2031,2032,2034,2036,2038],{"class":77,"line":368},[75,2033,450],{"class":219},[75,2035,528],{"class":223},[75,2037,479],{"class":81},[75,2039,459],{"class":223},[75,2041,2042],{"class":77,"line":374},[75,2043,270],{"emptyLinePlaceholder":269},[75,2045,2046,2048,2050,2052,2054,2056,2058,2060],{"class":77,"line":391},[75,2047,283],{"class":219},[75,2049,1058],{"class":286},[75,2051,290],{"class":219},[75,2053,528],{"class":223},[75,2055,531],{"class":81},[75,2057,296],{"class":223},[75,2059,536],{"class":85},[75,2061,302],{"class":223},[75,2063,2064,2066,2068,2070,2072,2074],{"class":77,"line":396},[75,2065,283],{"class":219},[75,2067,1092],{"class":286},[75,2069,290],{"class":219},[75,2071,315],{"class":219},[75,2073,1099],{"class":81},[75,2075,1102],{"class":223},[75,2077,2078],{"class":77,"line":409},[75,2079,270],{"emptyLinePlaceholder":269},[75,2081,2082],{"class":77,"line":420},[75,2083,2084],{"class":276},"// Track metrics\n",[75,2086,2087,2090,2093,2096,2099],{"class":77,"line":437},[75,2088,2089],{"class":219},"let",[75,2091,2092],{"class":223}," totalQueries ",[75,2094,2095],{"class":219},"=",[75,2097,2098],{"class":286}," 0",[75,2100,233],{"class":223},[75,2102,2103,2105,2108,2110,2112],{"class":77,"line":442},[75,2104,2089],{"class":219},[75,2106,2107],{"class":223}," avgResponseTime ",[75,2109,2095],{"class":219},[75,2111,2098],{"class":286},[75,2113,233],{"class":223},[75,2115,2116],{"class":77,"line":447},[75,2117,270],{"emptyLinePlaceholder":269},[75,2119,2120,2123,2125,2127,2130,2132,2134],{"class":77,"line":462},[75,2121,2122],{"class":223},"observe.",[75,2124,601],{"class":81},[75,2126,689],{"class":223},[75,2128,2129],{"class":617},"event",[75,2131,621],{"class":223},[75,2133,624],{"class":219},[75,2135,627],{"class":223},[75,2137,2138,2141,2144],{"class":77,"line":490},[75,2139,2140],{"class":223},"  totalQueries",[75,2142,2143],{"class":219},"++",[75,2145,233],{"class":223},[75,2147,2148,2150,2153,2155,2158],{"class":77,"line":495},[75,2149,655],{"class":219},[75,2151,2152],{"class":286}," duration",[75,2154,290],{"class":219},[75,2156,2157],{"class":81}," Number",[75,2159,2160],{"class":223},"(event.payload.durationMs);\n",[75,2162,2163,2166,2168,2171,2174,2177,2179,2182,2184,2187,2190,2192],{"class":77,"line":518},[75,2164,2165],{"class":223},"  avgResponseTime ",[75,2167,2095],{"class":219},[75,2169,2170],{"class":223}," (avgResponseTime ",[75,2172,2173],{"class":219},"*",[75,2175,2176],{"class":223}," (totalQueries ",[75,2178,677],{"class":219},[75,2180,2181],{"class":286}," 1",[75,2183,621],{"class":223},[75,2185,2186],{"class":219},"+",[75,2188,2189],{"class":223}," duration) ",[75,2191,874],{"class":219},[75,2193,2194],{"class":223}," totalQueries;\n",[75,2196,2197,2199,2201,2203,2206,2209,2212,2215,2218],{"class":77,"line":541},[75,2198,1865],{"class":223},[75,2200,1868],{"class":81},[75,2202,296],{"class":223},[75,2204,2205],{"class":85},"`Avg response: ${",[75,2207,2208],{"class":223},"avgResponseTime",[75,2210,2211],{"class":85},"}ms | Total: ${",[75,2213,2214],{"class":223},"totalQueries",[75,2216,2217],{"class":85},"}`",[75,2219,302],{"class":223},[75,2221,2222,2225,2227],{"class":77,"line":559},[75,2223,2224],{"class":223},"}, { category: ",[75,2226,1370],{"class":85},[75,2228,968],{"class":223},[75,2230,2231],{"class":77,"line":578},[75,2232,270],{"emptyLinePlaceholder":269},[75,2234,2235,2237,2239,2241,2243,2245,2247],{"class":77,"line":583},[75,2236,2122],{"class":223},[75,2238,601],{"class":81},[75,2240,689],{"class":223},[75,2242,2129],{"class":617},[75,2244,621],{"class":223},[75,2246,624],{"class":219},[75,2248,627],{"class":223},[75,2250,2251,2253,2256,2258,2261,2264,2267],{"class":77,"line":589},[75,2252,1865],{"class":223},[75,2254,2255],{"class":81},"error",[75,2257,296],{"class":223},[75,2259,2260],{"class":85},"\"Agent failed:\"",[75,2262,2263],{"class":223},", event.payload.error, ",[75,2265,2266],{"class":85},"\"from\"",[75,2268,2269],{"class":223},", event.emittedBy);\n",[75,2271,2272,2274,2276],{"class":77,"line":595},[75,2273,2224],{"class":223},[75,2275,1428],{"class":85},[75,2277,968],{"class":223},[75,2279,2280],{"class":77,"line":630},[75,2281,270],{"emptyLinePlaceholder":269},[75,2283,2284],{"class":77,"line":636},[75,2285,2286],{"class":276},"// Monitor agent health\n",[75,2288,2289,2291,2294,2296,2299,2302,2304,2306],{"class":77,"line":647},[75,2290,283],{"class":219},[75,2292,2293],{"class":286}," workers",[75,2295,290],{"class":219},[75,2297,2298],{"class":223}," room.",[75,2300,2301],{"class":81},"findAgents",[75,2303,296],{"class":223},[75,2305,1020],{"class":85},[75,2307,302],{"class":223},[75,2309,2310,2313,2315,2317,2320,2323,2326],{"class":77,"line":652},[75,2311,2312],{"class":223},"console.",[75,2314,1868],{"class":81},[75,2316,296],{"class":223},[75,2318,2319],{"class":85},"\"Workers online:\"",[75,2321,2322],{"class":223},", workers.",[75,2324,2325],{"class":286},"length",[75,2327,302],{"class":223},[75,2329,2330],{"class":77,"line":702},[75,2331,270],{"emptyLinePlaceholder":269},[75,2333,2334,2337,2339,2341,2344,2347,2350,2352,2354],{"class":77,"line":728},[75,2335,2336],{"class":223},"room.",[75,2338,601],{"class":81},[75,2340,296],{"class":223},[75,2342,2343],{"class":85},"\"presenceLeave\"",[75,2345,2346],{"class":223},", (",[75,2348,2349],{"class":617},"actorId",[75,2351,621],{"class":223},[75,2353,624],{"class":219},[75,2355,627],{"class":223},[75,2357,2358,2360,2363,2365,2368,2371,2374,2377,2380,2382,2384],{"class":77,"line":734},[75,2359,1865],{"class":223},[75,2361,2362],{"class":81},"warn",[75,2364,296],{"class":223},[75,2366,2367],{"class":85},"\"Agent left:\"",[75,2369,2370],{"class":223},", actorId, ",[75,2372,2373],{"class":85},"\"remaining:\"",[75,2375,2376],{"class":223},", room.",[75,2378,2379],{"class":81},"getConnectedAgents",[75,2381,669],{"class":223},[75,2383,2325],{"class":286},[75,2385,302],{"class":223},[75,2387,2388],{"class":77,"line":740},[75,2389,371],{"class":223},[24,2391,2393],{"id":2392},"what-you-get","What You Get",[162,2395,2396,2399,2402,2405,2408,2411],{},[35,2397,2398],{},"A chat interface with typing indicators and presence - users don't know they're talking to an AI",[35,2400,2401],{},"Horizontally scalable AI workers, load balanced across a group when you enable it on the client",[35,2403,2404],{},"Human escalation for sensitive conversations",[35,2406,2407],{},"Observability: response times, error rates, agent health",[35,2409,2410],{},"The last ten messages passed as conversation context on every task",[35,2412,2413],{},"Per-topic access control separating user traffic from agent traffic",[11,2415,2416],{},"The entire system runs on NoLag's real-time infrastructure. No Redis queues, no custom WebSocket servers, no separate monitoring stack.",[11,2418,2419],{},[193,2420,2422],{"href":2421},"/docs/agents/getting-started","Get started with @nolag/agents →",[2424,2425,2426],"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":236,"depth":236,"links":2428},[2429,2430,2431,2432,2433,2434,2435,2436],{"id":26,"depth":236,"text":27},{"id":59,"depth":236,"text":60},{"id":98,"depth":236,"text":99},{"id":200,"depth":236,"text":201},{"id":888,"depth":236,"text":889},{"id":1458,"depth":236,"text":1459},{"id":1902,"depth":236,"text":1903},{"id":2392,"depth":236,"text":2393},"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":2439},"blog/building-agentic-chatbot","PhW0KekSQNo_lb45lq06cax_x5EqktkRbVHywGq5DcY",1789869416248]