[{"data":1,"prerenderedAt":2854},["ShallowReactive",2],{"docs:/docs/concepts/presence":3},{"id":4,"title":5,"body":6,"description":2847,"extension":2848,"meta":2849,"navigation":151,"path":2850,"seo":2851,"stem":2852,"__hash__":2853},"docs/docs/concepts/presence.md","Presence Tracking",{"type":7,"value":8,"toc":2826},"minimark",[9,13,17,22,25,31,47,52,76,106,110,113,721,725,728,745,749,756,1024,1029,1070,1073,1077,1084,1226,1230,1233,1804,1808,1811,1921,1925,1928,1943,1946,2114,2117,2187,2191,2194,2235,2238,2241,2244,2247,2260,2264,2273,2464,2468,2474,2519,2527,2530,2596,2605,2609,2619,2705,2708,2714,2751,2757,2761,2793,2797,2822],[10,11,5],"h1",{"id":12},"presence-tracking",[14,15,16],"p",{},"Know who's online in real-time. Track user presence across your project and handle join/leave events.",[18,19,21],"h2",{"id":20},"what-is-presence","What is Presence?",[14,23,24],{},"Presence tracking allows you to see which actors (users, devices, services, or AI\nagents) are currently connected to your project. It covers two distinct jobs.",[14,26,27],{},[28,29,30],"strong",{},"For people:",[32,33,34,38,41,44],"ul",{},[35,36,37],"li",{},"Online/offline indicators in chat apps",[35,39,40],{},"Showing who's viewing a document",[35,42,43],{},"Live user counts",[35,45,46],{},"Typing indicators",[14,48,49],{},[28,50,51],{},"For backend workers, devices, and AI agents:",[32,53,54,57,60,68],{},[35,55,56],{},"Service discovery: which workers are up, and what each can do",[35,58,59],{},"Routing work to an actor that advertises the right capability",[35,61,62,63,67],{},"Detecting a worker that dropped, without polling or a ",[64,65,66],"code",{},"last_seen"," table",[35,69,70,71],{},"Keeping a scaled-to-zero service or sleeping device addressable, and waking it\non demand, with ",[72,73,75],"a",{"href":74},"#persistent-presence","Persistent Presence",[77,78,80],"callout",{"type":79},"info",[14,81,82,85,86,89,90,89,93,96,97,100,101,105],{},[28,83,84],{},"Client-level:"," Presence events (",[64,87,88],{},"presence:join",", ",[64,91,92],{},"presence:leave",[64,94,95],{},"presence:update",") are tracked at the client level, not room-scoped. Use ",[64,98,99],{},"client.on()"," to listen for these events. For observing presence across rooms, use ",[72,102,104],{"href":103},"/docs/concepts/lobbies","Lobbies",".",[18,107,109],{"id":108},"setting-presence","Setting Presence",[14,111,112],{},"Set your presence after connecting. Presence data can include any custom fields you need:",[114,115,116,462,589],"code-tabs",{},[117,118,124],"pre",{"className":119,"code":120,"filename":121,"language":122,"meta":123,"style":123},"language-typescript shiki shiki-themes github-light github-dark","import { NoLag } from '@nolag/js-sdk'\n\nconst client = NoLag('your_access_token')\nawait client.connect()\n\n// Set presence at the client level\nclient.setPresence({\n  username: 'Alice',\n  status: 'online',\n  avatar: 'https://example.com/alice.jpg'\n})\n\n// Presence events are client-level, not room-scoped\nclient.on('presence:join', (actor) => {\n  console.log(`${actor.presence.username} joined`)\n})\n\nclient.on('presence:leave', (actor) => {\n  console.log(`${actor.presence.username} left`)\n})\n\nclient.on('presence:update', (actor) => {\n  console.log(`${actor.presence.username} updated status to ${actor.presence.status}`)\n})\n","TypeScript","typescript","",[64,125,126,146,153,179,194,199,206,218,230,241,250,256,261,267,296,327,332,337,359,385,390,395,417,457],{"__ignoreMap":123},[127,128,131,135,139,142],"span",{"class":129,"line":130},"line",1,[127,132,134],{"class":133},"szBVR","import",[127,136,138],{"class":137},"sVt8B"," { NoLag } ",[127,140,141],{"class":133},"from",[127,143,145],{"class":144},"sZZnC"," '@nolag/js-sdk'\n",[127,147,149],{"class":129,"line":148},2,[127,150,152],{"emptyLinePlaceholder":151},true,"\n",[127,154,156,159,163,166,170,173,176],{"class":129,"line":155},3,[127,157,158],{"class":133},"const",[127,160,162],{"class":161},"sj4cs"," client",[127,164,165],{"class":133}," =",[127,167,169],{"class":168},"sScJk"," NoLag",[127,171,172],{"class":137},"(",[127,174,175],{"class":144},"'your_access_token'",[127,177,178],{"class":137},")\n",[127,180,182,185,188,191],{"class":129,"line":181},4,[127,183,184],{"class":133},"await",[127,186,187],{"class":137}," client.",[127,189,190],{"class":168},"connect",[127,192,193],{"class":137},"()\n",[127,195,197],{"class":129,"line":196},5,[127,198,152],{"emptyLinePlaceholder":151},[127,200,202],{"class":129,"line":201},6,[127,203,205],{"class":204},"sJ8bj","// Set presence at the client level\n",[127,207,209,212,215],{"class":129,"line":208},7,[127,210,211],{"class":137},"client.",[127,213,214],{"class":168},"setPresence",[127,216,217],{"class":137},"({\n",[127,219,221,224,227],{"class":129,"line":220},8,[127,222,223],{"class":137},"  username: ",[127,225,226],{"class":144},"'Alice'",[127,228,229],{"class":137},",\n",[127,231,233,236,239],{"class":129,"line":232},9,[127,234,235],{"class":137},"  status: ",[127,237,238],{"class":144},"'online'",[127,240,229],{"class":137},[127,242,244,247],{"class":129,"line":243},10,[127,245,246],{"class":137},"  avatar: ",[127,248,249],{"class":144},"'https://example.com/alice.jpg'\n",[127,251,253],{"class":129,"line":252},11,[127,254,255],{"class":137},"})\n",[127,257,259],{"class":129,"line":258},12,[127,260,152],{"emptyLinePlaceholder":151},[127,262,264],{"class":129,"line":263},13,[127,265,266],{"class":204},"// Presence events are client-level, not room-scoped\n",[127,268,270,272,275,277,280,283,287,290,293],{"class":129,"line":269},14,[127,271,211],{"class":137},[127,273,274],{"class":168},"on",[127,276,172],{"class":137},[127,278,279],{"class":144},"'presence:join'",[127,281,282],{"class":137},", (",[127,284,286],{"class":285},"s4XuR","actor",[127,288,289],{"class":137},") ",[127,291,292],{"class":133},"=>",[127,294,295],{"class":137}," {\n",[127,297,299,302,305,307,310,312,314,317,319,322,325],{"class":129,"line":298},15,[127,300,301],{"class":137},"  console.",[127,303,304],{"class":168},"log",[127,306,172],{"class":137},[127,308,309],{"class":144},"`${",[127,311,286],{"class":137},[127,313,105],{"class":144},[127,315,316],{"class":137},"presence",[127,318,105],{"class":144},[127,320,321],{"class":137},"username",[127,323,324],{"class":144},"} joined`",[127,326,178],{"class":137},[127,328,330],{"class":129,"line":329},16,[127,331,255],{"class":137},[127,333,335],{"class":129,"line":334},17,[127,336,152],{"emptyLinePlaceholder":151},[127,338,340,342,344,346,349,351,353,355,357],{"class":129,"line":339},18,[127,341,211],{"class":137},[127,343,274],{"class":168},[127,345,172],{"class":137},[127,347,348],{"class":144},"'presence:leave'",[127,350,282],{"class":137},[127,352,286],{"class":285},[127,354,289],{"class":137},[127,356,292],{"class":133},[127,358,295],{"class":137},[127,360,362,364,366,368,370,372,374,376,378,380,383],{"class":129,"line":361},19,[127,363,301],{"class":137},[127,365,304],{"class":168},[127,367,172],{"class":137},[127,369,309],{"class":144},[127,371,286],{"class":137},[127,373,105],{"class":144},[127,375,316],{"class":137},[127,377,105],{"class":144},[127,379,321],{"class":137},[127,381,382],{"class":144},"} left`",[127,384,178],{"class":137},[127,386,388],{"class":129,"line":387},20,[127,389,255],{"class":137},[127,391,393],{"class":129,"line":392},21,[127,394,152],{"emptyLinePlaceholder":151},[127,396,398,400,402,404,407,409,411,413,415],{"class":129,"line":397},22,[127,399,211],{"class":137},[127,401,274],{"class":168},[127,403,172],{"class":137},[127,405,406],{"class":144},"'presence:update'",[127,408,282],{"class":137},[127,410,286],{"class":285},[127,412,289],{"class":137},[127,414,292],{"class":133},[127,416,295],{"class":137},[127,418,420,422,424,426,428,430,432,434,436,438,441,443,445,447,449,452,455],{"class":129,"line":419},23,[127,421,301],{"class":137},[127,423,304],{"class":168},[127,425,172],{"class":137},[127,427,309],{"class":144},[127,429,286],{"class":137},[127,431,105],{"class":144},[127,433,316],{"class":137},[127,435,105],{"class":144},[127,437,321],{"class":137},[127,439,440],{"class":144},"} updated status to ${",[127,442,286],{"class":137},[127,444,105],{"class":144},[127,446,316],{"class":137},[127,448,105],{"class":144},[127,450,451],{"class":137},"status",[127,453,454],{"class":144},"}`",[127,456,178],{"class":137},[127,458,460],{"class":129,"line":459},24,[127,461,255],{"class":137},[117,463,468],{"className":464,"code":465,"filename":466,"language":467,"meta":123,"style":123},"language-python shiki shiki-themes github-light github-dark","from nolag import NoLag\n\nclient = NoLag('your_access_token')\nawait client.connect()\n\n# Set presence at the client level\nawait client.set_presence({\n    'username': 'Alice',\n    'status': 'online',\n    'avatar': 'https://example.com/alice.jpg'\n})\n\n# Presence events are client-level, not room-scoped\ndef on_join(actor):\n    print(f\"{actor.presence['username']} joined\")\n\ndef on_leave(actor):\n    print(f\"{actor.presence['username']} left\")\n\ndef on_update(actor):\n    print(f\"{actor.presence['username']} updated status\")\n\nclient.on('presence:join', on_join)\nclient.on('presence:leave', on_leave)\nclient.on('presence:update', on_update)\n","Python","python",[64,469,470,475,479,484,489,493,498,503,508,513,518,522,526,531,536,541,545,550,555,559,564,569,573,578,583],{"__ignoreMap":123},[127,471,472],{"class":129,"line":130},[127,473,474],{},"from nolag import NoLag\n",[127,476,477],{"class":129,"line":148},[127,478,152],{"emptyLinePlaceholder":151},[127,480,481],{"class":129,"line":155},[127,482,483],{},"client = NoLag('your_access_token')\n",[127,485,486],{"class":129,"line":181},[127,487,488],{},"await client.connect()\n",[127,490,491],{"class":129,"line":196},[127,492,152],{"emptyLinePlaceholder":151},[127,494,495],{"class":129,"line":201},[127,496,497],{},"# Set presence at the client level\n",[127,499,500],{"class":129,"line":208},[127,501,502],{},"await client.set_presence({\n",[127,504,505],{"class":129,"line":220},[127,506,507],{},"    'username': 'Alice',\n",[127,509,510],{"class":129,"line":232},[127,511,512],{},"    'status': 'online',\n",[127,514,515],{"class":129,"line":243},[127,516,517],{},"    'avatar': 'https://example.com/alice.jpg'\n",[127,519,520],{"class":129,"line":252},[127,521,255],{},[127,523,524],{"class":129,"line":258},[127,525,152],{"emptyLinePlaceholder":151},[127,527,528],{"class":129,"line":263},[127,529,530],{},"# Presence events are client-level, not room-scoped\n",[127,532,533],{"class":129,"line":269},[127,534,535],{},"def on_join(actor):\n",[127,537,538],{"class":129,"line":298},[127,539,540],{},"    print(f\"{actor.presence['username']} joined\")\n",[127,542,543],{"class":129,"line":329},[127,544,152],{"emptyLinePlaceholder":151},[127,546,547],{"class":129,"line":334},[127,548,549],{},"def on_leave(actor):\n",[127,551,552],{"class":129,"line":339},[127,553,554],{},"    print(f\"{actor.presence['username']} left\")\n",[127,556,557],{"class":129,"line":361},[127,558,152],{"emptyLinePlaceholder":151},[127,560,561],{"class":129,"line":387},[127,562,563],{},"def on_update(actor):\n",[127,565,566],{"class":129,"line":392},[127,567,568],{},"    print(f\"{actor.presence['username']} updated status\")\n",[127,570,571],{"class":129,"line":397},[127,572,152],{"emptyLinePlaceholder":151},[127,574,575],{"class":129,"line":419},[127,576,577],{},"client.on('presence:join', on_join)\n",[127,579,580],{"class":129,"line":459},[127,581,582],{},"client.on('presence:leave', on_leave)\n",[127,584,586],{"class":129,"line":585},25,[127,587,588],{},"client.on('presence:update', on_update)\n",[117,590,595],{"className":591,"code":592,"filename":593,"language":594,"meta":123,"style":123},"language-go shiki shiki-themes github-light github-dark","package main\n\nimport (\n    \"fmt\"\n\n    nolag \"github.com/NoLagApp/go-sdk\"\n)\n\nfunc main() {\n    client := nolag.New(\"your_access_token\")\n    client.Connect()\n\n    // Set presence at the client level\n    client.SetPresence(map[string]any{\n        \"username\": \"Alice\",\n        \"status\":   \"online\",\n        \"avatar\":   \"https://example.com/alice.jpg\",\n    })\n\n    // Listen for presence events (join, leave, update)\n    client.On(\"presence\", func(args ...any) {\n        topic, _ := args[0].(string)\n        data, _ := args[1].(map[string]any)\n        fmt.Printf(\"Presence event on %s: %v\\n\", topic, data)\n    })\n}\n","Go","go",[64,596,597,602,606,611,616,620,625,629,633,638,643,648,652,657,662,667,672,677,682,686,691,696,701,706,711,715],{"__ignoreMap":123},[127,598,599],{"class":129,"line":130},[127,600,601],{},"package main\n",[127,603,604],{"class":129,"line":148},[127,605,152],{"emptyLinePlaceholder":151},[127,607,608],{"class":129,"line":155},[127,609,610],{},"import (\n",[127,612,613],{"class":129,"line":181},[127,614,615],{},"    \"fmt\"\n",[127,617,618],{"class":129,"line":196},[127,619,152],{"emptyLinePlaceholder":151},[127,621,622],{"class":129,"line":201},[127,623,624],{},"    nolag \"github.com/NoLagApp/go-sdk\"\n",[127,626,627],{"class":129,"line":208},[127,628,178],{},[127,630,631],{"class":129,"line":220},[127,632,152],{"emptyLinePlaceholder":151},[127,634,635],{"class":129,"line":232},[127,636,637],{},"func main() {\n",[127,639,640],{"class":129,"line":243},[127,641,642],{},"    client := nolag.New(\"your_access_token\")\n",[127,644,645],{"class":129,"line":252},[127,646,647],{},"    client.Connect()\n",[127,649,650],{"class":129,"line":258},[127,651,152],{"emptyLinePlaceholder":151},[127,653,654],{"class":129,"line":263},[127,655,656],{},"    // Set presence at the client level\n",[127,658,659],{"class":129,"line":269},[127,660,661],{},"    client.SetPresence(map[string]any{\n",[127,663,664],{"class":129,"line":298},[127,665,666],{},"        \"username\": \"Alice\",\n",[127,668,669],{"class":129,"line":329},[127,670,671],{},"        \"status\":   \"online\",\n",[127,673,674],{"class":129,"line":334},[127,675,676],{},"        \"avatar\":   \"https://example.com/alice.jpg\",\n",[127,678,679],{"class":129,"line":339},[127,680,681],{},"    })\n",[127,683,684],{"class":129,"line":361},[127,685,152],{"emptyLinePlaceholder":151},[127,687,688],{"class":129,"line":387},[127,689,690],{},"    // Listen for presence events (join, leave, update)\n",[127,692,693],{"class":129,"line":392},[127,694,695],{},"    client.On(\"presence\", func(args ...any) {\n",[127,697,698],{"class":129,"line":397},[127,699,700],{},"        topic, _ := args[0].(string)\n",[127,702,703],{"class":129,"line":419},[127,704,705],{},"        data, _ := args[1].(map[string]any)\n",[127,707,708],{"class":129,"line":459},[127,709,710],{},"        fmt.Printf(\"Presence event on %s: %v\\n\", topic, data)\n",[127,712,713],{"class":129,"line":585},[127,714,681],{},[127,716,718],{"class":129,"line":717},26,[127,719,720],{},"}\n",[18,722,724],{"id":723},"presence-events","Presence Events",[14,726,727],{},"Listen for these events to track when actors come online, go offline, or update their status:",[32,729,730,735,740],{},[35,731,732,734],{},[64,733,88],{}," - An actor connected and set their presence",[35,736,737,739],{},[64,738,92],{}," - An actor disconnected",[35,741,742,744],{},[64,743,95],{}," - An actor updated their presence data",[18,746,748],{"id":747},"getting-current-presence","Getting Current Presence",[14,750,751,752,755],{},"Get a list of all actors currently present. Note that ",[64,753,754],{},"getPresence()"," returns all actors from the local cache (not room-scoped):",[114,757,758,913,971],{},[117,759,761],{"className":119,"code":760,"filename":121,"language":122,"meta":123,"style":123},"// Get all present actors from local cache (client-level)\nconst actors = client.getPresence()\nconsole.log('Users online:', actors.length)\n\nactors.forEach((actor) => {\n  console.log(`- ${actor.presence.username} (${actor.actorTokenId})`)\n})\n\n// Fetch fresh presence list from server\nconst freshList = await client.fetchPresence()\nconsole.log('Server says online:', freshList.length)\n",[64,762,763,768,784,804,808,827,863,867,871,876,895],{"__ignoreMap":123},[127,764,765],{"class":129,"line":130},[127,766,767],{"class":204},"// Get all present actors from local cache (client-level)\n",[127,769,770,772,775,777,779,782],{"class":129,"line":148},[127,771,158],{"class":133},[127,773,774],{"class":161}," actors",[127,776,165],{"class":133},[127,778,187],{"class":137},[127,780,781],{"class":168},"getPresence",[127,783,193],{"class":137},[127,785,786,789,791,793,796,799,802],{"class":129,"line":155},[127,787,788],{"class":137},"console.",[127,790,304],{"class":168},[127,792,172],{"class":137},[127,794,795],{"class":144},"'Users online:'",[127,797,798],{"class":137},", actors.",[127,800,801],{"class":161},"length",[127,803,178],{"class":137},[127,805,806],{"class":129,"line":181},[127,807,152],{"emptyLinePlaceholder":151},[127,809,810,813,816,819,821,823,825],{"class":129,"line":196},[127,811,812],{"class":137},"actors.",[127,814,815],{"class":168},"forEach",[127,817,818],{"class":137},"((",[127,820,286],{"class":285},[127,822,289],{"class":137},[127,824,292],{"class":133},[127,826,295],{"class":137},[127,828,829,831,833,835,838,840,842,844,846,848,851,853,855,858,861],{"class":129,"line":201},[127,830,301],{"class":137},[127,832,304],{"class":168},[127,834,172],{"class":137},[127,836,837],{"class":144},"`- ${",[127,839,286],{"class":137},[127,841,105],{"class":144},[127,843,316],{"class":137},[127,845,105],{"class":144},[127,847,321],{"class":137},[127,849,850],{"class":144},"} (${",[127,852,286],{"class":137},[127,854,105],{"class":144},[127,856,857],{"class":137},"actorTokenId",[127,859,860],{"class":144},"})`",[127,862,178],{"class":137},[127,864,865],{"class":129,"line":208},[127,866,255],{"class":137},[127,868,869],{"class":129,"line":220},[127,870,152],{"emptyLinePlaceholder":151},[127,872,873],{"class":129,"line":232},[127,874,875],{"class":204},"// Fetch fresh presence list from server\n",[127,877,878,880,883,885,888,890,893],{"class":129,"line":243},[127,879,158],{"class":133},[127,881,882],{"class":161}," freshList",[127,884,165],{"class":133},[127,886,887],{"class":133}," await",[127,889,187],{"class":137},[127,891,892],{"class":168},"fetchPresence",[127,894,193],{"class":137},[127,896,897,899,901,903,906,909,911],{"class":129,"line":252},[127,898,788],{"class":137},[127,900,304],{"class":168},[127,902,172],{"class":137},[127,904,905],{"class":144},"'Server says online:'",[127,907,908],{"class":137},", freshList.",[127,910,801],{"class":161},[127,912,178],{"class":137},[117,914,916],{"className":464,"code":915,"filename":466,"language":467,"meta":123,"style":123},"# Get all present actors from local cache (client-level)\nactors = client.get_all_presence()\nprint('Users online:', len(actors))\n\nfor actor in actors:\n    print(f\"- {actor.presence['username']} ({actor.actor_token_id})\")\n\n# Get a specific actor's presence\nalice = client.get_presence('actor_token_id_here')\nif alice:\n    print(f\"Alice is {alice.presence['status']}\")\n",[64,917,918,923,928,933,937,942,947,951,956,961,966],{"__ignoreMap":123},[127,919,920],{"class":129,"line":130},[127,921,922],{},"# Get all present actors from local cache (client-level)\n",[127,924,925],{"class":129,"line":148},[127,926,927],{},"actors = client.get_all_presence()\n",[127,929,930],{"class":129,"line":155},[127,931,932],{},"print('Users online:', len(actors))\n",[127,934,935],{"class":129,"line":181},[127,936,152],{"emptyLinePlaceholder":151},[127,938,939],{"class":129,"line":196},[127,940,941],{},"for actor in actors:\n",[127,943,944],{"class":129,"line":201},[127,945,946],{},"    print(f\"- {actor.presence['username']} ({actor.actor_token_id})\")\n",[127,948,949],{"class":129,"line":208},[127,950,152],{"emptyLinePlaceholder":151},[127,952,953],{"class":129,"line":220},[127,954,955],{},"# Get a specific actor's presence\n",[127,957,958],{"class":129,"line":232},[127,959,960],{},"alice = client.get_presence('actor_token_id_here')\n",[127,962,963],{"class":129,"line":243},[127,964,965],{},"if alice:\n",[127,967,968],{"class":129,"line":252},[127,969,970],{},"    print(f\"Alice is {alice.presence['status']}\")\n",[117,972,974],{"className":591,"code":973,"filename":593,"language":594,"meta":123,"style":123},"// Get presence for actors on a topic\nroom := client.SetApp(\"my-app\").SetRoom(\"general\")\nactors, err := client.GetPresence(room.Prefix() + \"/messages\")\nif err == nil {\n    fmt.Println(\"Users online:\", len(actors))\n\n    for _, actor := range actors {\n        fmt.Printf(\"- %s (%s)\\n\", actor.Presence[\"username\"], actor.ActorTokenID)\n    }\n}\n",[64,975,976,981,986,991,996,1001,1005,1010,1015,1020],{"__ignoreMap":123},[127,977,978],{"class":129,"line":130},[127,979,980],{},"// Get presence for actors on a topic\n",[127,982,983],{"class":129,"line":148},[127,984,985],{},"room := client.SetApp(\"my-app\").SetRoom(\"general\")\n",[127,987,988],{"class":129,"line":155},[127,989,990],{},"actors, err := client.GetPresence(room.Prefix() + \"/messages\")\n",[127,992,993],{"class":129,"line":181},[127,994,995],{},"if err == nil {\n",[127,997,998],{"class":129,"line":196},[127,999,1000],{},"    fmt.Println(\"Users online:\", len(actors))\n",[127,1002,1003],{"class":129,"line":201},[127,1004,152],{"emptyLinePlaceholder":151},[127,1006,1007],{"class":129,"line":208},[127,1008,1009],{},"    for _, actor := range actors {\n",[127,1011,1012],{"class":129,"line":220},[127,1013,1014],{},"        fmt.Printf(\"- %s (%s)\\n\", actor.Presence[\"username\"], actor.ActorTokenID)\n",[127,1016,1017],{"class":129,"line":232},[127,1018,1019],{},"    }\n",[127,1021,1022],{"class":129,"line":243},[127,1023,720],{},[1025,1026,1028],"h3",{"id":1027},"local-cache-vs-server-fetch","Local Cache vs Server Fetch",[32,1030,1031,1049,1061],{},[35,1032,1033,1036,1037,1040,1041,1044,1045,1048],{},[28,1034,1035],{},"JavaScript:"," ",[64,1038,1039],{},"client.getPresence()"," returns all, ",[64,1042,1043],{},"client.getPresence(actorId)"," returns one, ",[64,1046,1047],{},"client.fetchPresence()"," fetches from server",[35,1050,1051,1036,1054,1040,1057,1060],{},[28,1052,1053],{},"Python:",[64,1055,1056],{},"client.get_all_presence()",[64,1058,1059],{},"client.get_presence(actor_token_id)"," returns one",[35,1062,1063,1036,1066,1069],{},[28,1064,1065],{},"Go:",[64,1067,1068],{},"client.GetPresence(topic)"," fetches presence for a specific topic from the server",[14,1071,1072],{},"The local cache is automatically updated when you receive presence events.",[18,1074,1076],{"id":1075},"updating-presence","Updating Presence",[14,1078,1079,1080,1083],{},"Update your presence data at any time by calling ",[64,1081,1082],{},"setPresence()"," again:",[114,1085,1086,1144,1185],{},[117,1087,1089],{"className":119,"code":1088,"filename":121,"language":122,"meta":123,"style":123},"// Update your presence data (e.g., status change)\nclient.setPresence({\n  username: 'Alice',\n  status: 'away',\n  lastActive: Date.now()\n})\n\n// Client-level presence is automatically re-sent on reconnect\n",[64,1090,1091,1096,1104,1112,1121,1131,1135,1139],{"__ignoreMap":123},[127,1092,1093],{"class":129,"line":130},[127,1094,1095],{"class":204},"// Update your presence data (e.g., status change)\n",[127,1097,1098,1100,1102],{"class":129,"line":148},[127,1099,211],{"class":137},[127,1101,214],{"class":168},[127,1103,217],{"class":137},[127,1105,1106,1108,1110],{"class":129,"line":155},[127,1107,223],{"class":137},[127,1109,226],{"class":144},[127,1111,229],{"class":137},[127,1113,1114,1116,1119],{"class":129,"line":181},[127,1115,235],{"class":137},[127,1117,1118],{"class":144},"'away'",[127,1120,229],{"class":137},[127,1122,1123,1126,1129],{"class":129,"line":196},[127,1124,1125],{"class":137},"  lastActive: Date.",[127,1127,1128],{"class":168},"now",[127,1130,193],{"class":137},[127,1132,1133],{"class":129,"line":201},[127,1134,255],{"class":137},[127,1136,1137],{"class":129,"line":208},[127,1138,152],{"emptyLinePlaceholder":151},[127,1140,1141],{"class":129,"line":220},[127,1142,1143],{"class":204},"// Client-level presence is automatically re-sent on reconnect\n",[117,1145,1147],{"className":464,"code":1146,"filename":466,"language":467,"meta":123,"style":123},"# Update your presence data (e.g., status change)\nawait client.set_presence({\n    'username': 'Alice',\n    'status': 'away',\n    'lastActive': time.time()\n})\n\n# Client-level presence is automatically re-sent on reconnect\n",[64,1148,1149,1154,1158,1162,1167,1172,1176,1180],{"__ignoreMap":123},[127,1150,1151],{"class":129,"line":130},[127,1152,1153],{},"# Update your presence data (e.g., status change)\n",[127,1155,1156],{"class":129,"line":148},[127,1157,502],{},[127,1159,1160],{"class":129,"line":155},[127,1161,507],{},[127,1163,1164],{"class":129,"line":181},[127,1165,1166],{},"    'status': 'away',\n",[127,1168,1169],{"class":129,"line":196},[127,1170,1171],{},"    'lastActive': time.time()\n",[127,1173,1174],{"class":129,"line":201},[127,1175,255],{},[127,1177,1178],{"class":129,"line":208},[127,1179,152],{"emptyLinePlaceholder":151},[127,1181,1182],{"class":129,"line":220},[127,1183,1184],{},"# Client-level presence is automatically re-sent on reconnect\n",[117,1186,1188],{"className":591,"code":1187,"filename":593,"language":594,"meta":123,"style":123},"// Update your presence data (e.g., status change)\nclient.SetPresence(map[string]any{\n    \"username\":   \"Alice\",\n    \"status\":     \"away\",\n    \"lastActive\": time.Now().Unix(),\n})\n\n// Client-level presence is automatically re-sent on reconnect\n",[64,1189,1190,1194,1199,1204,1209,1214,1218,1222],{"__ignoreMap":123},[127,1191,1192],{"class":129,"line":130},[127,1193,1095],{},[127,1195,1196],{"class":129,"line":148},[127,1197,1198],{},"client.SetPresence(map[string]any{\n",[127,1200,1201],{"class":129,"line":155},[127,1202,1203],{},"    \"username\":   \"Alice\",\n",[127,1205,1206],{"class":129,"line":181},[127,1207,1208],{},"    \"status\":     \"away\",\n",[127,1210,1211],{"class":129,"line":196},[127,1212,1213],{},"    \"lastActive\": time.Now().Unix(),\n",[127,1215,1216],{"class":129,"line":201},[127,1217,255],{},[127,1219,1220],{"class":129,"line":208},[127,1221,152],{"emptyLinePlaceholder":151},[127,1223,1224],{"class":129,"line":220},[127,1225,1143],{},[18,1227,1229],{"id":1228},"typing-indicators","Typing Indicators",[14,1231,1232],{},"A common use case is showing typing indicators. Use presence updates with a debounce:",[114,1234,1235,1481,1653],{},[117,1236,1238],{"className":119,"code":1237,"filename":121,"language":122,"meta":123,"style":123},"// For typing indicators, update presence with typing status\nlet typingTimeout: NodeJS.Timeout\n\nfunction sendTyping() {\n  client.setPresence({\n    username: 'Alice',\n    status: 'online',\n    isTyping: true\n  })\n\n  // Clear typing after 3 seconds of inactivity\n  clearTimeout(typingTimeout)\n  typingTimeout = setTimeout(() => {\n    client.setPresence({\n      username: 'Alice',\n      status: 'online',\n      isTyping: false\n    })\n  }, 3000)\n}\n\n// Listen for typing from others (client-level event)\nclient.on('presence:update', (actor) => {\n  if (actor.presence.isTyping) {\n    showTypingIndicator(actor.presence.username)\n  } else {\n    hideTypingIndicator(actor.presence.username)\n  }\n})\n",[64,1239,1240,1245,1264,1268,1279,1288,1297,1306,1314,1319,1323,1328,1336,1354,1363,1372,1381,1389,1393,1403,1407,1411,1416,1436,1444,1452,1462,1470,1476],{"__ignoreMap":123},[127,1241,1242],{"class":129,"line":130},[127,1243,1244],{"class":204},"// For typing indicators, update presence with typing status\n",[127,1246,1247,1250,1253,1256,1259,1261],{"class":129,"line":148},[127,1248,1249],{"class":133},"let",[127,1251,1252],{"class":137}," typingTimeout",[127,1254,1255],{"class":133},":",[127,1257,1258],{"class":168}," NodeJS",[127,1260,105],{"class":137},[127,1262,1263],{"class":168},"Timeout\n",[127,1265,1266],{"class":129,"line":155},[127,1267,152],{"emptyLinePlaceholder":151},[127,1269,1270,1273,1276],{"class":129,"line":181},[127,1271,1272],{"class":133},"function",[127,1274,1275],{"class":168}," sendTyping",[127,1277,1278],{"class":137},"() {\n",[127,1280,1281,1284,1286],{"class":129,"line":196},[127,1282,1283],{"class":137},"  client.",[127,1285,214],{"class":168},[127,1287,217],{"class":137},[127,1289,1290,1293,1295],{"class":129,"line":201},[127,1291,1292],{"class":137},"    username: ",[127,1294,226],{"class":144},[127,1296,229],{"class":137},[127,1298,1299,1302,1304],{"class":129,"line":208},[127,1300,1301],{"class":137},"    status: ",[127,1303,238],{"class":144},[127,1305,229],{"class":137},[127,1307,1308,1311],{"class":129,"line":220},[127,1309,1310],{"class":137},"    isTyping: ",[127,1312,1313],{"class":161},"true\n",[127,1315,1316],{"class":129,"line":232},[127,1317,1318],{"class":137},"  })\n",[127,1320,1321],{"class":129,"line":243},[127,1322,152],{"emptyLinePlaceholder":151},[127,1324,1325],{"class":129,"line":252},[127,1326,1327],{"class":204},"  // Clear typing after 3 seconds of inactivity\n",[127,1329,1330,1333],{"class":129,"line":258},[127,1331,1332],{"class":168},"  clearTimeout",[127,1334,1335],{"class":137},"(typingTimeout)\n",[127,1337,1338,1341,1344,1347,1350,1352],{"class":129,"line":263},[127,1339,1340],{"class":137},"  typingTimeout ",[127,1342,1343],{"class":133},"=",[127,1345,1346],{"class":168}," setTimeout",[127,1348,1349],{"class":137},"(() ",[127,1351,292],{"class":133},[127,1353,295],{"class":137},[127,1355,1356,1359,1361],{"class":129,"line":269},[127,1357,1358],{"class":137},"    client.",[127,1360,214],{"class":168},[127,1362,217],{"class":137},[127,1364,1365,1368,1370],{"class":129,"line":298},[127,1366,1367],{"class":137},"      username: ",[127,1369,226],{"class":144},[127,1371,229],{"class":137},[127,1373,1374,1377,1379],{"class":129,"line":329},[127,1375,1376],{"class":137},"      status: ",[127,1378,238],{"class":144},[127,1380,229],{"class":137},[127,1382,1383,1386],{"class":129,"line":334},[127,1384,1385],{"class":137},"      isTyping: ",[127,1387,1388],{"class":161},"false\n",[127,1390,1391],{"class":129,"line":339},[127,1392,681],{"class":137},[127,1394,1395,1398,1401],{"class":129,"line":361},[127,1396,1397],{"class":137},"  }, ",[127,1399,1400],{"class":161},"3000",[127,1402,178],{"class":137},[127,1404,1405],{"class":129,"line":387},[127,1406,720],{"class":137},[127,1408,1409],{"class":129,"line":392},[127,1410,152],{"emptyLinePlaceholder":151},[127,1412,1413],{"class":129,"line":397},[127,1414,1415],{"class":204},"// Listen for typing from others (client-level event)\n",[127,1417,1418,1420,1422,1424,1426,1428,1430,1432,1434],{"class":129,"line":419},[127,1419,211],{"class":137},[127,1421,274],{"class":168},[127,1423,172],{"class":137},[127,1425,406],{"class":144},[127,1427,282],{"class":137},[127,1429,286],{"class":285},[127,1431,289],{"class":137},[127,1433,292],{"class":133},[127,1435,295],{"class":137},[127,1437,1438,1441],{"class":129,"line":459},[127,1439,1440],{"class":133},"  if",[127,1442,1443],{"class":137}," (actor.presence.isTyping) {\n",[127,1445,1446,1449],{"class":129,"line":585},[127,1447,1448],{"class":168},"    showTypingIndicator",[127,1450,1451],{"class":137},"(actor.presence.username)\n",[127,1453,1454,1457,1460],{"class":129,"line":717},[127,1455,1456],{"class":137},"  } ",[127,1458,1459],{"class":133},"else",[127,1461,295],{"class":137},[127,1463,1465,1468],{"class":129,"line":1464},27,[127,1466,1467],{"class":168},"    hideTypingIndicator",[127,1469,1451],{"class":137},[127,1471,1473],{"class":129,"line":1472},28,[127,1474,1475],{"class":137},"  }\n",[127,1477,1479],{"class":129,"line":1478},29,[127,1480,255],{"class":137},[117,1482,1484],{"className":464,"code":1483,"filename":466,"language":467,"meta":123,"style":123},"# For typing indicators, update presence with typing status\ntyping_task = None\n\nasync def send_typing():\n    global typing_task\n\n    await client.set_presence({\n        'username': 'Alice',\n        'status': 'online',\n        'isTyping': True\n    })\n\n    # Clear typing after 3 seconds of inactivity\n    if typing_task:\n        typing_task.cancel()\n\n    async def clear_typing():\n        await asyncio.sleep(3)\n        await client.set_presence({\n            'username': 'Alice',\n            'status': 'online',\n            'isTyping': False\n        })\n\n    typing_task = asyncio.create_task(clear_typing())\n\n# Listen for typing from others (client-level event)\ndef on_presence_update(actor):\n    if actor.presence.get('isTyping'):\n        show_typing_indicator(actor.presence['username'])\n    else:\n        hide_typing_indicator(actor.presence['username'])\n\nclient.on('presence:update', on_presence_update)\n",[64,1485,1486,1491,1496,1500,1505,1510,1514,1519,1524,1529,1534,1538,1542,1547,1552,1557,1561,1566,1571,1576,1581,1586,1591,1596,1600,1605,1609,1614,1619,1624,1630,1636,1642,1647],{"__ignoreMap":123},[127,1487,1488],{"class":129,"line":130},[127,1489,1490],{},"# For typing indicators, update presence with typing status\n",[127,1492,1493],{"class":129,"line":148},[127,1494,1495],{},"typing_task = None\n",[127,1497,1498],{"class":129,"line":155},[127,1499,152],{"emptyLinePlaceholder":151},[127,1501,1502],{"class":129,"line":181},[127,1503,1504],{},"async def send_typing():\n",[127,1506,1507],{"class":129,"line":196},[127,1508,1509],{},"    global typing_task\n",[127,1511,1512],{"class":129,"line":201},[127,1513,152],{"emptyLinePlaceholder":151},[127,1515,1516],{"class":129,"line":208},[127,1517,1518],{},"    await client.set_presence({\n",[127,1520,1521],{"class":129,"line":220},[127,1522,1523],{},"        'username': 'Alice',\n",[127,1525,1526],{"class":129,"line":232},[127,1527,1528],{},"        'status': 'online',\n",[127,1530,1531],{"class":129,"line":243},[127,1532,1533],{},"        'isTyping': True\n",[127,1535,1536],{"class":129,"line":252},[127,1537,681],{},[127,1539,1540],{"class":129,"line":258},[127,1541,152],{"emptyLinePlaceholder":151},[127,1543,1544],{"class":129,"line":263},[127,1545,1546],{},"    # Clear typing after 3 seconds of inactivity\n",[127,1548,1549],{"class":129,"line":269},[127,1550,1551],{},"    if typing_task:\n",[127,1553,1554],{"class":129,"line":298},[127,1555,1556],{},"        typing_task.cancel()\n",[127,1558,1559],{"class":129,"line":329},[127,1560,152],{"emptyLinePlaceholder":151},[127,1562,1563],{"class":129,"line":334},[127,1564,1565],{},"    async def clear_typing():\n",[127,1567,1568],{"class":129,"line":339},[127,1569,1570],{},"        await asyncio.sleep(3)\n",[127,1572,1573],{"class":129,"line":361},[127,1574,1575],{},"        await client.set_presence({\n",[127,1577,1578],{"class":129,"line":387},[127,1579,1580],{},"            'username': 'Alice',\n",[127,1582,1583],{"class":129,"line":392},[127,1584,1585],{},"            'status': 'online',\n",[127,1587,1588],{"class":129,"line":397},[127,1589,1590],{},"            'isTyping': False\n",[127,1592,1593],{"class":129,"line":419},[127,1594,1595],{},"        })\n",[127,1597,1598],{"class":129,"line":459},[127,1599,152],{"emptyLinePlaceholder":151},[127,1601,1602],{"class":129,"line":585},[127,1603,1604],{},"    typing_task = asyncio.create_task(clear_typing())\n",[127,1606,1607],{"class":129,"line":717},[127,1608,152],{"emptyLinePlaceholder":151},[127,1610,1611],{"class":129,"line":1464},[127,1612,1613],{},"# Listen for typing from others (client-level event)\n",[127,1615,1616],{"class":129,"line":1472},[127,1617,1618],{},"def on_presence_update(actor):\n",[127,1620,1621],{"class":129,"line":1478},[127,1622,1623],{},"    if actor.presence.get('isTyping'):\n",[127,1625,1627],{"class":129,"line":1626},30,[127,1628,1629],{},"        show_typing_indicator(actor.presence['username'])\n",[127,1631,1633],{"class":129,"line":1632},31,[127,1634,1635],{},"    else:\n",[127,1637,1639],{"class":129,"line":1638},32,[127,1640,1641],{},"        hide_typing_indicator(actor.presence['username'])\n",[127,1643,1645],{"class":129,"line":1644},33,[127,1646,152],{"emptyLinePlaceholder":151},[127,1648,1650],{"class":129,"line":1649},34,[127,1651,1652],{},"client.on('presence:update', on_presence_update)\n",[117,1654,1656],{"className":591,"code":1655,"filename":593,"language":594,"meta":123,"style":123},"// For typing indicators, update presence with typing status\nvar typingTimer *time.Timer\n\nfunc sendTyping() {\n    client.SetPresence(map[string]any{\n        \"username\": \"Alice\",\n        \"status\":   \"online\",\n        \"isTyping\": true,\n    })\n\n    // Clear typing after 3 seconds of inactivity\n    if typingTimer != nil {\n        typingTimer.Stop()\n    }\n    typingTimer = time.AfterFunc(3*time.Second, func() {\n        client.SetPresence(map[string]any{\n            \"username\": \"Alice\",\n            \"status\":   \"online\",\n            \"isTyping\": false,\n        })\n    })\n}\n\n// Listen for presence events\nclient.On(\"presence\", func(args ...any) {\n    data, _ := args[1].(map[string]any)\n    if isTyping, ok := data[\"isTyping\"].(bool); ok && isTyping {\n        showTypingIndicator(data[\"username\"].(string))\n    } else {\n        hideTypingIndicator(data[\"username\"].(string))\n    }\n})\n",[64,1657,1658,1662,1667,1671,1676,1680,1684,1688,1693,1697,1701,1706,1711,1716,1720,1725,1730,1735,1740,1745,1749,1753,1757,1761,1766,1771,1776,1781,1786,1791,1796,1800],{"__ignoreMap":123},[127,1659,1660],{"class":129,"line":130},[127,1661,1244],{},[127,1663,1664],{"class":129,"line":148},[127,1665,1666],{},"var typingTimer *time.Timer\n",[127,1668,1669],{"class":129,"line":155},[127,1670,152],{"emptyLinePlaceholder":151},[127,1672,1673],{"class":129,"line":181},[127,1674,1675],{},"func sendTyping() {\n",[127,1677,1678],{"class":129,"line":196},[127,1679,661],{},[127,1681,1682],{"class":129,"line":201},[127,1683,666],{},[127,1685,1686],{"class":129,"line":208},[127,1687,671],{},[127,1689,1690],{"class":129,"line":220},[127,1691,1692],{},"        \"isTyping\": true,\n",[127,1694,1695],{"class":129,"line":232},[127,1696,681],{},[127,1698,1699],{"class":129,"line":243},[127,1700,152],{"emptyLinePlaceholder":151},[127,1702,1703],{"class":129,"line":252},[127,1704,1705],{},"    // Clear typing after 3 seconds of inactivity\n",[127,1707,1708],{"class":129,"line":258},[127,1709,1710],{},"    if typingTimer != nil {\n",[127,1712,1713],{"class":129,"line":263},[127,1714,1715],{},"        typingTimer.Stop()\n",[127,1717,1718],{"class":129,"line":269},[127,1719,1019],{},[127,1721,1722],{"class":129,"line":298},[127,1723,1724],{},"    typingTimer = time.AfterFunc(3*time.Second, func() {\n",[127,1726,1727],{"class":129,"line":329},[127,1728,1729],{},"        client.SetPresence(map[string]any{\n",[127,1731,1732],{"class":129,"line":334},[127,1733,1734],{},"            \"username\": \"Alice\",\n",[127,1736,1737],{"class":129,"line":339},[127,1738,1739],{},"            \"status\":   \"online\",\n",[127,1741,1742],{"class":129,"line":361},[127,1743,1744],{},"            \"isTyping\": false,\n",[127,1746,1747],{"class":129,"line":387},[127,1748,1595],{},[127,1750,1751],{"class":129,"line":392},[127,1752,681],{},[127,1754,1755],{"class":129,"line":397},[127,1756,720],{},[127,1758,1759],{"class":129,"line":419},[127,1760,152],{"emptyLinePlaceholder":151},[127,1762,1763],{"class":129,"line":459},[127,1764,1765],{},"// Listen for presence events\n",[127,1767,1768],{"class":129,"line":585},[127,1769,1770],{},"client.On(\"presence\", func(args ...any) {\n",[127,1772,1773],{"class":129,"line":717},[127,1774,1775],{},"    data, _ := args[1].(map[string]any)\n",[127,1777,1778],{"class":129,"line":1464},[127,1779,1780],{},"    if isTyping, ok := data[\"isTyping\"].(bool); ok && isTyping {\n",[127,1782,1783],{"class":129,"line":1472},[127,1784,1785],{},"        showTypingIndicator(data[\"username\"].(string))\n",[127,1787,1788],{"class":129,"line":1478},[127,1789,1790],{},"    } else {\n",[127,1792,1793],{"class":129,"line":1626},[127,1794,1795],{},"        hideTypingIndicator(data[\"username\"].(string))\n",[127,1797,1798],{"class":129,"line":1632},[127,1799,1019],{},[127,1801,1802],{"class":129,"line":1638},[127,1803,255],{},[18,1805,1807],{"id":1806},"actor-presence-structure","Actor Presence Structure",[14,1809,1810],{},"Each actor presence object contains:",[114,1812,1813],{},[117,1814,1816],{"className":119,"code":1815,"filename":121,"language":122,"meta":123,"style":123},"interface ActorPresence {\n  actorTokenId: string    // Unique actor identifier\n  actorType: 'device' | 'user' | 'server'\n  presence: {             // Your custom presence data\n    username?: string\n    status?: string\n    // ... any other fields you set\n  }\n  joinedAt?: number       // Timestamp when actor connected\n}\n",[64,1817,1818,1828,1841,1862,1875,1886,1895,1900,1904,1917],{"__ignoreMap":123},[127,1819,1820,1823,1826],{"class":129,"line":130},[127,1821,1822],{"class":133},"interface",[127,1824,1825],{"class":168}," ActorPresence",[127,1827,295],{"class":137},[127,1829,1830,1833,1835,1838],{"class":129,"line":148},[127,1831,1832],{"class":285},"  actorTokenId",[127,1834,1255],{"class":133},[127,1836,1837],{"class":161}," string",[127,1839,1840],{"class":204},"    // Unique actor identifier\n",[127,1842,1843,1846,1848,1851,1854,1857,1859],{"class":129,"line":155},[127,1844,1845],{"class":285},"  actorType",[127,1847,1255],{"class":133},[127,1849,1850],{"class":144}," 'device'",[127,1852,1853],{"class":133}," |",[127,1855,1856],{"class":144}," 'user'",[127,1858,1853],{"class":133},[127,1860,1861],{"class":144}," 'server'\n",[127,1863,1864,1867,1869,1872],{"class":129,"line":181},[127,1865,1866],{"class":285},"  presence",[127,1868,1255],{"class":133},[127,1870,1871],{"class":137}," {             ",[127,1873,1874],{"class":204},"// Your custom presence data\n",[127,1876,1877,1880,1883],{"class":129,"line":196},[127,1878,1879],{"class":285},"    username",[127,1881,1882],{"class":133},"?:",[127,1884,1885],{"class":161}," string\n",[127,1887,1888,1891,1893],{"class":129,"line":201},[127,1889,1890],{"class":285},"    status",[127,1892,1882],{"class":133},[127,1894,1885],{"class":161},[127,1896,1897],{"class":129,"line":208},[127,1898,1899],{"class":204},"    // ... any other fields you set\n",[127,1901,1902],{"class":129,"line":220},[127,1903,1475],{"class":137},[127,1905,1906,1909,1911,1914],{"class":129,"line":232},[127,1907,1908],{"class":285},"  joinedAt",[127,1910,1882],{"class":133},[127,1912,1913],{"class":161}," number",[127,1915,1916],{"class":204},"       // Timestamp when actor connected\n",[127,1918,1919],{"class":129,"line":243},[127,1920,720],{"class":137},[18,1922,1924],{"id":1923},"backend-workers-and-agents","Backend Workers and Agents",[14,1926,1927],{},"Presence is not only a chat feature. It is also how a backend service, worker\npool, or AI agent announces that it is running and what it can do, and how an\norchestrator discovers it.",[77,1929,1930],{"type":79},[14,1931,1932,1935,1936,1939,1940,1942],{},[28,1933,1934],{},"Do not mirror presence into your own database."," A ",[64,1937,1938],{},"last_seen_at"," column plus a\nperiodic \"anything older than N minutes is dead\" sweep is the usual way this gets\nrebuilt, and it is strictly worse: the broker already knows the moment a socket\ndrops and emits ",[64,1941,92],{}," in realtime, where a staleness sweep is only as\nfresh as its interval. Store durable records if you need history or audit, but\nread liveness from presence.",[14,1944,1945],{},"A worker advertises itself the same way a user does, with the payload describing\ncapability rather than identity:",[114,1947,1948,2038,2076],{},[117,1949,1951],{"className":119,"code":1950,"filename":121,"language":122,"meta":123,"style":123},"const room = client.setApp('agents').setRoom('workers')\n\nroom.setPresence({\n  name: 'echo-runtime-a91f',\n  role: 'agent',\n  capabilities: ['chat_response', 'soil_analysis'],\n})\n",[64,1952,1953,1985,1989,1998,2008,2018,2034],{"__ignoreMap":123},[127,1954,1955,1957,1960,1962,1964,1967,1969,1972,1975,1978,1980,1983],{"class":129,"line":130},[127,1956,158],{"class":133},[127,1958,1959],{"class":161}," room",[127,1961,165],{"class":133},[127,1963,187],{"class":137},[127,1965,1966],{"class":168},"setApp",[127,1968,172],{"class":137},[127,1970,1971],{"class":144},"'agents'",[127,1973,1974],{"class":137},").",[127,1976,1977],{"class":168},"setRoom",[127,1979,172],{"class":137},[127,1981,1982],{"class":144},"'workers'",[127,1984,178],{"class":137},[127,1986,1987],{"class":129,"line":148},[127,1988,152],{"emptyLinePlaceholder":151},[127,1990,1991,1994,1996],{"class":129,"line":155},[127,1992,1993],{"class":137},"room.",[127,1995,214],{"class":168},[127,1997,217],{"class":137},[127,1999,2000,2003,2006],{"class":129,"line":181},[127,2001,2002],{"class":137},"  name: ",[127,2004,2005],{"class":144},"'echo-runtime-a91f'",[127,2007,229],{"class":137},[127,2009,2010,2013,2016],{"class":129,"line":196},[127,2011,2012],{"class":137},"  role: ",[127,2014,2015],{"class":144},"'agent'",[127,2017,229],{"class":137},[127,2019,2020,2023,2026,2028,2031],{"class":129,"line":201},[127,2021,2022],{"class":137},"  capabilities: [",[127,2024,2025],{"class":144},"'chat_response'",[127,2027,89],{"class":137},[127,2029,2030],{"class":144},"'soil_analysis'",[127,2032,2033],{"class":137},"],\n",[127,2035,2036],{"class":129,"line":208},[127,2037,255],{"class":137},[117,2039,2041],{"className":464,"code":2040,"filename":466,"language":467,"meta":123,"style":123},"room = client.set_app('agents').set_room('workers')\n\nawait room.set_presence({\n    'name': 'echo-runtime-a91f',\n    'role': 'agent',\n    'capabilities': ['chat_response', 'soil_analysis'],\n})\n",[64,2042,2043,2048,2052,2057,2062,2067,2072],{"__ignoreMap":123},[127,2044,2045],{"class":129,"line":130},[127,2046,2047],{},"room = client.set_app('agents').set_room('workers')\n",[127,2049,2050],{"class":129,"line":148},[127,2051,152],{"emptyLinePlaceholder":151},[127,2053,2054],{"class":129,"line":155},[127,2055,2056],{},"await room.set_presence({\n",[127,2058,2059],{"class":129,"line":181},[127,2060,2061],{},"    'name': 'echo-runtime-a91f',\n",[127,2063,2064],{"class":129,"line":196},[127,2065,2066],{},"    'role': 'agent',\n",[127,2068,2069],{"class":129,"line":201},[127,2070,2071],{},"    'capabilities': ['chat_response', 'soil_analysis'],\n",[127,2073,2074],{"class":129,"line":208},[127,2075,255],{},[117,2077,2079],{"className":591,"code":2078,"filename":593,"language":594,"meta":123,"style":123},"room := client.SetApp(\"agents\").SetRoom(\"workers\")\n\nroom.SetPresence(map[string]any{\n    \"name\":         \"echo-runtime-a91f\",\n    \"role\":         \"agent\",\n    \"capabilities\": []string{\"chat_response\", \"soil_analysis\"},\n})\n",[64,2080,2081,2086,2090,2095,2100,2105,2110],{"__ignoreMap":123},[127,2082,2083],{"class":129,"line":130},[127,2084,2085],{},"room := client.SetApp(\"agents\").SetRoom(\"workers\")\n",[127,2087,2088],{"class":129,"line":148},[127,2089,152],{"emptyLinePlaceholder":151},[127,2091,2092],{"class":129,"line":155},[127,2093,2094],{},"room.SetPresence(map[string]any{\n",[127,2096,2097],{"class":129,"line":181},[127,2098,2099],{},"    \"name\":         \"echo-runtime-a91f\",\n",[127,2101,2102],{"class":129,"line":196},[127,2103,2104],{},"    \"role\":         \"agent\",\n",[127,2106,2107],{"class":129,"line":201},[127,2108,2109],{},"    \"capabilities\": []string{\"chat_response\", \"soil_analysis\"},\n",[127,2111,2112],{"class":129,"line":208},[127,2113,255],{},[14,2115,2116],{},"An orchestrator then reads the live set to route work, with no registry of its own:",[117,2118,2120],{"className":119,"code":2119,"filename":121,"language":122,"meta":123,"style":123},"// Room.getPresence() returns a Record keyed by actorTokenId, not an array\nconst available = Object.values(room.getPresence())\n  .filter((a) => (a.presence.capabilities as string[])?.includes('chat_response'))\n",[64,2121,2122,2127,2150],{"__ignoreMap":123},[127,2123,2124],{"class":129,"line":130},[127,2125,2126],{"class":204},"// Room.getPresence() returns a Record keyed by actorTokenId, not an array\n",[127,2128,2129,2131,2134,2136,2139,2142,2145,2147],{"class":129,"line":148},[127,2130,158],{"class":133},[127,2132,2133],{"class":161}," available",[127,2135,165],{"class":133},[127,2137,2138],{"class":137}," Object.",[127,2140,2141],{"class":168},"values",[127,2143,2144],{"class":137},"(room.",[127,2146,781],{"class":168},[127,2148,2149],{"class":137},"())\n",[127,2151,2152,2155,2158,2160,2162,2164,2166,2169,2172,2174,2177,2180,2182,2184],{"class":129,"line":155},[127,2153,2154],{"class":137},"  .",[127,2156,2157],{"class":168},"filter",[127,2159,818],{"class":137},[127,2161,72],{"class":285},[127,2163,289],{"class":137},[127,2165,292],{"class":133},[127,2167,2168],{"class":137}," (a.presence.capabilities ",[127,2170,2171],{"class":133},"as",[127,2173,1837],{"class":161},[127,2175,2176],{"class":137},"[])?.",[127,2178,2179],{"class":168},"includes",[127,2181,172],{"class":137},[127,2183,2025],{"class":144},[127,2185,2186],{"class":137},"))\n",[1025,2188,2190],{"id":2189},"connection-liveness-is-not-task-liveness","Connection liveness is not task liveness",[14,2192,2193],{},"These are different questions and presence only answers the first:",[2195,2196,2197,2210],"table",{},[2198,2199,2200],"thead",{},[2201,2202,2203,2207],"tr",{},[2204,2205,2206],"th",{},"Question",[2204,2208,2209],{},"Use",[2211,2212,2213,2227],"tbody",{},[2201,2214,2215,2219],{},[2216,2217,2218],"td",{},"Is this worker connected?",[2216,2220,2221,2222,2224,2225],{},"Presence: ",[64,2223,88],{}," / ",[64,2226,92],{},[2201,2228,2229,2232],{},[2216,2230,2231],{},"Is this worker still making progress on task X?",[2216,2233,2234],{},"An application-level progress signal",[14,2236,2237],{},"A worker can hold a healthy socket while a task is wedged, so presence will keep\nreporting it online. If you need to fail a stalled task, publish progress events\nfrom the worker and run a watchdog that resets on each one. That is a legitimate\napplication concern, not a gap in presence, and the two mechanisms belong side by\nside.",[18,2239,75],{"id":2240},"persistent-presence",[14,2242,2243],{},"By default a presence record is ephemeral: it exists while the socket is\nconnected and disappears on disconnect. That does not suit a service that scales\nto zero, a device that sleeps, or an agent you want to remain discoverable and\naddressable while it is not running.",[14,2245,2246],{},"Persistent Presence keeps the record after the socket drops, so the actor stays\ndiscoverable, and optionally lets NoLag wake it when work arrives.",[77,2248,2249],{"type":79},[14,2250,2251,2252,2255,2256,2259],{},"Persistent Presence is available on NoLag cloud. A self-hosted or standalone\nbroker runs with the durable presence store and wake dispatcher disabled, where\n",[64,2253,2254],{},"persistent"," and ",[64,2257,2258],{},"wake"," are accepted and ignored, so the same code stays portable\nand simply behaves as ephemeral presence.",[1025,2261,2263],{"id":2262},"advertising-a-persistent-actor","Advertising a persistent actor",[14,2265,2266,2267,2269,2270,2272],{},"Add ",[64,2268,2254],{}," and, if the actor can be woken, a ",[64,2271,2258],{}," block:",[114,2274,2275,2359,2412],{},[117,2276,2278],{"className":119,"code":2277,"filename":121,"language":122,"meta":123,"style":123},"room.setPresence({\n  name: 'soil-sensor-14',\n  role: 'device',\n  capabilities: ['telemetry'],\n  persistent: true,\n  wake: {\n    url: 'https://example.com/hooks/nolag-wake',\n    timeoutMs: 10000,\n  },\n})\n",[64,2279,2280,2288,2297,2306,2315,2325,2330,2340,2350,2355],{"__ignoreMap":123},[127,2281,2282,2284,2286],{"class":129,"line":130},[127,2283,1993],{"class":137},[127,2285,214],{"class":168},[127,2287,217],{"class":137},[127,2289,2290,2292,2295],{"class":129,"line":148},[127,2291,2002],{"class":137},[127,2293,2294],{"class":144},"'soil-sensor-14'",[127,2296,229],{"class":137},[127,2298,2299,2301,2304],{"class":129,"line":155},[127,2300,2012],{"class":137},[127,2302,2303],{"class":144},"'device'",[127,2305,229],{"class":137},[127,2307,2308,2310,2313],{"class":129,"line":181},[127,2309,2022],{"class":137},[127,2311,2312],{"class":144},"'telemetry'",[127,2314,2033],{"class":137},[127,2316,2317,2320,2323],{"class":129,"line":196},[127,2318,2319],{"class":137},"  persistent: ",[127,2321,2322],{"class":161},"true",[127,2324,229],{"class":137},[127,2326,2327],{"class":129,"line":201},[127,2328,2329],{"class":137},"  wake: {\n",[127,2331,2332,2335,2338],{"class":129,"line":208},[127,2333,2334],{"class":137},"    url: ",[127,2336,2337],{"class":144},"'https://example.com/hooks/nolag-wake'",[127,2339,229],{"class":137},[127,2341,2342,2345,2348],{"class":129,"line":220},[127,2343,2344],{"class":137},"    timeoutMs: ",[127,2346,2347],{"class":161},"10000",[127,2349,229],{"class":137},[127,2351,2352],{"class":129,"line":232},[127,2353,2354],{"class":137},"  },\n",[127,2356,2357],{"class":129,"line":243},[127,2358,255],{"class":137},[117,2360,2362],{"className":464,"code":2361,"filename":466,"language":467,"meta":123,"style":123},"await room.set_presence({\n    'name': 'soil-sensor-14',\n    'role': 'device',\n    'capabilities': ['telemetry'],\n    'persistent': True,\n    'wake': {\n        'url': 'https://example.com/hooks/nolag-wake',\n        'timeoutMs': 10000,\n    },\n})\n",[64,2363,2364,2368,2373,2378,2383,2388,2393,2398,2403,2408],{"__ignoreMap":123},[127,2365,2366],{"class":129,"line":130},[127,2367,2056],{},[127,2369,2370],{"class":129,"line":148},[127,2371,2372],{},"    'name': 'soil-sensor-14',\n",[127,2374,2375],{"class":129,"line":155},[127,2376,2377],{},"    'role': 'device',\n",[127,2379,2380],{"class":129,"line":181},[127,2381,2382],{},"    'capabilities': ['telemetry'],\n",[127,2384,2385],{"class":129,"line":196},[127,2386,2387],{},"    'persistent': True,\n",[127,2389,2390],{"class":129,"line":201},[127,2391,2392],{},"    'wake': {\n",[127,2394,2395],{"class":129,"line":208},[127,2396,2397],{},"        'url': 'https://example.com/hooks/nolag-wake',\n",[127,2399,2400],{"class":129,"line":220},[127,2401,2402],{},"        'timeoutMs': 10000,\n",[127,2404,2405],{"class":129,"line":232},[127,2406,2407],{},"    },\n",[127,2409,2410],{"class":129,"line":243},[127,2411,255],{},[117,2413,2415],{"className":591,"code":2414,"filename":593,"language":594,"meta":123,"style":123},"room.SetPresence(map[string]any{\n    \"name\":         \"soil-sensor-14\",\n    \"role\":         \"device\",\n    \"capabilities\": []string{\"telemetry\"},\n    \"persistent\":   true,\n    \"wake\": map[string]any{\n        \"url\":       \"https://example.com/hooks/nolag-wake\",\n        \"timeoutMs\": 10000,\n    },\n})\n",[64,2416,2417,2421,2426,2431,2436,2441,2446,2451,2456,2460],{"__ignoreMap":123},[127,2418,2419],{"class":129,"line":130},[127,2420,2094],{},[127,2422,2423],{"class":129,"line":148},[127,2424,2425],{},"    \"name\":         \"soil-sensor-14\",\n",[127,2427,2428],{"class":129,"line":155},[127,2429,2430],{},"    \"role\":         \"device\",\n",[127,2432,2433],{"class":129,"line":181},[127,2434,2435],{},"    \"capabilities\": []string{\"telemetry\"},\n",[127,2437,2438],{"class":129,"line":196},[127,2439,2440],{},"    \"persistent\":   true,\n",[127,2442,2443],{"class":129,"line":201},[127,2444,2445],{},"    \"wake\": map[string]any{\n",[127,2447,2448],{"class":129,"line":208},[127,2449,2450],{},"        \"url\":       \"https://example.com/hooks/nolag-wake\",\n",[127,2452,2453],{"class":129,"line":220},[127,2454,2455],{},"        \"timeoutMs\": 10000,\n",[127,2457,2458],{"class":129,"line":232},[127,2459,2407],{},[127,2461,2462],{"class":129,"line":243},[127,2463,255],{},[1025,2465,2467],{"id":2466},"lifecycle","Lifecycle",[14,2469,2470,2471,2473],{},"A persistent record carries a ",[64,2472,451],{}," that an ephemeral one does not:",[2195,2475,2476,2487],{},[2198,2477,2478],{},[2201,2479,2480,2484],{},[2204,2481,2482],{},[64,2483,451],{},[2204,2485,2486],{},"Meaning",[2211,2488,2489,2499,2509],{},[2201,2490,2491,2496],{},[2216,2492,2493],{},[64,2494,2495],{},"online",[2216,2497,2498],{},"Socket connected right now",[2201,2500,2501,2506],{},[2216,2502,2503],{},[64,2504,2505],{},"offline",[2216,2507,2508],{},"Registered but disconnected. Still discoverable, still addressable",[2201,2510,2511,2516],{},[2216,2512,2513],{},[64,2514,2515],{},"waking",[2216,2517,2518],{},"A wake webhook has been fired and NoLag is waiting for the reconnect",[117,2520,2525],{"className":2521,"code":2523,"language":2524},[2522],"language-text","   advertise(persistent)          socket drops\n ─────────────────────▶ online ─────────────────▶ offline\n                          ▲                          │\n                          │                          │ message routed to it\n       reconnects, queued │                          ▼\n       messages flush     └───────────────────── waking\n","text",[64,2526,2523],{"__ignoreMap":123},[14,2528,2529],{},"Discovery returns offline actors too, so filter on status when you only want\nwhat is live right now:",[117,2531,2533],{"className":119,"code":2532,"filename":121,"language":122,"meta":123,"style":123},"const live = Object.values(room.getPresence()).filter(\n  (a) => a.status === undefined || a.status === 'online',\n)\n",[64,2534,2535,2560,2592],{"__ignoreMap":123},[127,2536,2537,2539,2542,2544,2546,2548,2550,2552,2555,2557],{"class":129,"line":130},[127,2538,158],{"class":133},[127,2540,2541],{"class":161}," live",[127,2543,165],{"class":133},[127,2545,2138],{"class":137},[127,2547,2141],{"class":168},[127,2549,2144],{"class":137},[127,2551,781],{"class":168},[127,2553,2554],{"class":137},"()).",[127,2556,2157],{"class":168},[127,2558,2559],{"class":137},"(\n",[127,2561,2562,2565,2567,2569,2571,2574,2577,2580,2583,2585,2587,2590],{"class":129,"line":148},[127,2563,2564],{"class":137},"  (",[127,2566,72],{"class":285},[127,2568,289],{"class":137},[127,2570,292],{"class":133},[127,2572,2573],{"class":137}," a.status ",[127,2575,2576],{"class":133},"===",[127,2578,2579],{"class":161}," undefined",[127,2581,2582],{"class":133}," ||",[127,2584,2573],{"class":137},[127,2586,2576],{"class":133},[127,2588,2589],{"class":144}," 'online'",[127,2591,229],{"class":137},[127,2593,2594],{"class":129,"line":155},[127,2595,178],{"class":137},[14,2597,2598,2600,2601,2604],{},[64,2599,451],{}," is absent for ordinary ephemeral actors, which is why the check above\ntreats ",[64,2602,2603],{},"undefined"," as live.",[1025,2606,2608],{"id":2607},"wake-webhook","Wake webhook",[14,2610,2611,2612,2614,2615,2618],{},"When a message is routed to an ",[64,2613,2505],{}," persistent actor, NoLag queues the\nmessage on the actor's session and POSTs to the registered ",[64,2616,2617],{},"wake.url"," so the\nactor can cold-start and drain it. The webhook carries no message payload.",[117,2620,2625],{"className":2621,"code":2622,"filename":2623,"language":2624,"meta":123,"style":123},"language-json shiki shiki-themes github-light github-dark","{\n  \"appId\": \"019f...\",\n  \"roomId\": \"019f...\",\n  \"actorTokenId\": \"019f...\",\n  \"reason\": \"dispatch\",\n  \"wakeId\": \"a91f2c...\",\n  \"ts\": 1765200000000\n}\n","POST body","json",[64,2626,2627,2632,2645,2656,2667,2679,2691,2701],{"__ignoreMap":123},[127,2628,2629],{"class":129,"line":130},[127,2630,2631],{"class":137},"{\n",[127,2633,2634,2637,2640,2643],{"class":129,"line":148},[127,2635,2636],{"class":161},"  \"appId\"",[127,2638,2639],{"class":137},": ",[127,2641,2642],{"class":144},"\"019f...\"",[127,2644,229],{"class":137},[127,2646,2647,2650,2652,2654],{"class":129,"line":155},[127,2648,2649],{"class":161},"  \"roomId\"",[127,2651,2639],{"class":137},[127,2653,2642],{"class":144},[127,2655,229],{"class":137},[127,2657,2658,2661,2663,2665],{"class":129,"line":181},[127,2659,2660],{"class":161},"  \"actorTokenId\"",[127,2662,2639],{"class":137},[127,2664,2642],{"class":144},[127,2666,229],{"class":137},[127,2668,2669,2672,2674,2677],{"class":129,"line":196},[127,2670,2671],{"class":161},"  \"reason\"",[127,2673,2639],{"class":137},[127,2675,2676],{"class":144},"\"dispatch\"",[127,2678,229],{"class":137},[127,2680,2681,2684,2686,2689],{"class":129,"line":201},[127,2682,2683],{"class":161},"  \"wakeId\"",[127,2685,2639],{"class":137},[127,2687,2688],{"class":144},"\"a91f2c...\"",[127,2690,229],{"class":137},[127,2692,2693,2696,2698],{"class":129,"line":208},[127,2694,2695],{"class":161},"  \"ts\"",[127,2697,2639],{"class":137},[127,2699,2700],{"class":161},"1765200000000\n",[127,2702,2703],{"class":129,"line":220},[127,2704,720],{"class":137},[14,2706,2707],{},"Verify the signature before acting on it:",[117,2709,2712],{"className":2710,"code":2711,"language":2524},[2522],"x-nolag-signature: sha256=\u003Chex HMAC-SHA256 of the raw body>\n",[64,2713,2711],{"__ignoreMap":123},[32,2715,2716,2726,2729,2737,2744],{},[35,2717,2718,2719,2722,2723,2725],{},"Wakes are ",[28,2720,2721],{},"debounced",": one per actor per ",[64,2724,2515],{}," window, not one per message.",[35,2727,2728],{},"The call is fire-and-forget and never blocks the publisher.",[35,2730,2731,2734,2735,105],{},[64,2732,2733],{},"timeoutMs"," defaults to ",[64,2736,2347],{},[35,2738,2739,2740,2743],{},"Treat repeated ",[64,2741,2742],{},"wakeId"," values as no-ops; a cold start may already be in flight.",[35,2745,2746,2747,2750],{},"Respond ",[64,2748,2749],{},"2xx"," to acknowledge. NoLag then waits for the actor to reconnect, not\nfor the work to finish.",[77,2752,2754],{"type":2753},"warning",[14,2755,2756],{},"A failed wake is logged and dropped, not retried. If your endpoint is down when\nthe wake fires, the queued message waits for the actor to reconnect on its own.\nTreat the wake as a best-effort nudge rather than a delivery guarantee.",[18,2758,2760],{"id":2759},"best-practices","Best Practices",[32,2762,2763,2769,2775,2781,2787],{},[35,2764,2765,2768],{},[28,2766,2767],{},"Keep presence data small"," - Only include necessary information (username, status, avatar URL)",[35,2770,2771,2774],{},[28,2772,2773],{},"Use debouncing"," - For typing indicators, debounce updates to avoid flooding",[35,2776,2777,2780],{},[28,2778,2779],{},"Handle reconnections"," - Client-level presence is automatically re-sent on reconnect",[35,2782,2783,2786],{},[28,2784,2785],{},"Consider privacy"," - Let users opt out of presence tracking if needed",[35,2788,2789,2792],{},[28,2790,2791],{},"Use fetchPresence() sparingly"," - The local cache is usually sufficient",[18,2794,2796],{"id":2795},"next-steps","Next Steps",[32,2798,2799,2804,2810,2816],{},[35,2800,2801],{},[72,2802,2803],{"href":103},"Observe presence across rooms with Lobbies",[35,2805,2806],{},[72,2807,2809],{"href":2808},"/docs/concepts/topics","Learn about Topics",[35,2811,2812],{},[72,2813,2815],{"href":2814},"/docs/concepts/rooms","Organize with Rooms",[35,2817,2818],{},[72,2819,2821],{"href":2820},"/docs/guides/chat-app","Build a Chat App",[2823,2824,2825],"style",{},"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 .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}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);}",{"title":123,"searchDepth":148,"depth":148,"links":2827},[2828,2829,2830,2831,2834,2835,2836,2837,2840,2845,2846],{"id":20,"depth":148,"text":21},{"id":108,"depth":148,"text":109},{"id":723,"depth":148,"text":724},{"id":747,"depth":148,"text":748,"children":2832},[2833],{"id":1027,"depth":155,"text":1028},{"id":1075,"depth":148,"text":1076},{"id":1228,"depth":148,"text":1229},{"id":1806,"depth":148,"text":1807},{"id":1923,"depth":148,"text":1924,"children":2838},[2839],{"id":2189,"depth":155,"text":2190},{"id":2240,"depth":148,"text":75,"children":2841},[2842,2843,2844],{"id":2262,"depth":155,"text":2263},{"id":2466,"depth":155,"text":2467},{"id":2607,"depth":155,"text":2608},{"id":2759,"depth":148,"text":2760},{"id":2795,"depth":148,"text":2796},"Track presence in real-time with NoLag. Know who is online, handle join/leave events, discover backend workers and AI agents by capability, and keep scaled-to-zero actors addressable with Persistent Presence.","md",{},"/docs/concepts/presence",{"title":5,"description":2847},"docs/concepts/presence","1r9CkfmCvkmRbPCF-Uf6vOY5QAdsZOkr7kBrH_PJzn4",1788160343035]