[{"data":1,"prerenderedAt":1561},["ShallowReactive",2],{"docs:/docs/scopes/multi-tenancy":3},{"id":4,"title":5,"body":6,"description":1554,"extension":1555,"meta":1556,"navigation":166,"path":1557,"seo":1558,"stem":1559,"__hash__":1560},"docs/docs/scopes/multi-tenancy.md","Multi-Tenant Patterns with Access Scopes",{"type":7,"value":8,"toc":1536},"minimark",[9,13,17,46,73,78,81,84,89,102,106,125,422,433,437,440,443,446,457,460,638,641,645,648,651,662,665,923,927,935,971,974,984,1267,1270,1493,1507,1511,1532],[10,11,5],"h1",{"id":12},"multi-tenant-patterns-with-access-scopes",[14,15,16],"p",{},"Real-world patterns for using Access Scopes to build multi-tenant applications.",[14,18,19,20,24,25,28,29,32,33,32,36,39,40,45],{},"Every sample on this page uses the REST client from ",[21,22,23],"code",{},"@nolag/js-sdk"," with a project-scoped API key (",[21,26,27],{},"nlg_live_...","). The same calls are available as plain REST (",[21,30,31],{},"POST /v1/scopes",", ",[21,34,35],{},"POST /v1/actors",[21,37,38],{},"PATCH /v1/actors/:actorTokenId",") and from the Python and Go REST clients; see the ",[41,42,44],"a",{"href":43},"/docs/scopes/api-reference","API Reference",".",[47,48,50],"callout",{"type":49},"info",[14,51,52,56,57,60,61,64,65,68,69,45],{},[53,54,55],"strong",{},"No app grant step."," Apps are open by default: every actor in the project can reach every room in the app, so once an actor is scoped it can start subscribing. Only if you have made a room private (it has at least one grant) does the actor also need a room grant: ",[21,58,59],{},"POST /v1/apps/:appId/rooms/:roomId/actors"," with ",[21,62,63],{},"{ \"actorTokenId\", \"permission\": \"pubSub\" }",", or ",[21,66,67],{},"api.rooms.grantActor(appId, roomId, { actorTokenId, permission: \"pubSub\" })",". See ",[41,70,72],{"href":71},"/docs/concepts/acl","Access Control",[74,75,77],"h2",{"id":76},"pattern-1-one-scope-per-tenant-customer","Pattern 1: One Scope per Tenant Customer",[14,79,80],{},"The most common pattern. When a new customer signs up for your SaaS product, create a scope for them. All actors belonging to that customer are assigned to the scope, and their communication is automatically isolated from other tenants.",[14,82,83],{},"This eliminates the need to create separate apps or projects per tenant. A single app serves all tenants, with scopes providing the isolation boundary.",[85,86,88],"h3",{"id":87},"when-to-use","When to use",[90,91,92,96,99],"ul",{},[93,94,95],"li",{},"B2B SaaS where each customer organization needs isolated communication",[93,97,98],{},"White-label products where tenants must not see each other's data",[93,100,101],{},"Marketplace platforms where vendors operate independently",[85,103,105],{"id":104},"implementation","Implementation",[14,107,108,109,112,113,116,117,120,121,124],{},"The tenant slug becomes a topic segment, so it must match ",[21,110,111],{},"^[a-z0-9][a-z0-9-]*[a-z0-9]$"," and be unique within the project. In the comments below, ",[21,114,115],{},"my-app-a3f9"," is the app slug returned when the app was created (app slugs always carry a random suffix), and ",[21,118,119],{},"chat"," and ",[21,122,123],{},"notifications"," are rooms that already exist in that app.",[126,127,128],"code-tabs",{},[129,130,136],"pre",{"className":131,"code":132,"filename":133,"language":134,"meta":135,"style":135},"language-typescript shiki shiki-themes github-light github-dark","import { NoLagApi } from \"@nolag/js-sdk\";\n\nconst api = new NoLagApi(process.env.NOLAG_API_KEY); // nlg_live_...\n\n// When a new customer signs up, create a scope for them\nasync function onboardTenant(tenantSlug: string, tenantName: string) {\n  // 1. Create the scope\n  const scope = await api.scopes.create({ slug: tenantSlug, name: tenantName });\n\n  // 2. Create an actor for one of this tenant's users and bind it to the scope.\n  //    (Over raw REST, POST /v1/actors accepts accessScopeId in the same call.)\n  const actor = await api.actors.create({ name: \"user-alice\", actorType: \"user\" });\n  await api.scopes.addActor(scope.accessScopeId, actor.actorTokenId);\n\n  // 3. Hand actor.accessToken to Alice's client. It is returned once.\n\n  // Alice's client subscribes to my-app-a3f9/chat/messages as usual.\n  // The broker resolves her topics under the tenant slug:\n  //   my-app-a3f9/acme-corp/chat/messages\n  //   my-app-a3f9/acme-corp/notifications/alerts\n  return { scope, actor };\n}\n\nawait onboardTenant(\"acme-corp\", \"Acme Corporation\");\n","TypeScript","typescript","",[21,137,138,161,168,201,206,212,249,255,278,283,289,295,327,341,346,352,357,363,369,375,381,390,396,401],{"__ignoreMap":135},[139,140,143,147,151,154,158],"span",{"class":141,"line":142},"line",1,[139,144,146],{"class":145},"szBVR","import",[139,148,150],{"class":149},"sVt8B"," { NoLagApi } ",[139,152,153],{"class":145},"from",[139,155,157],{"class":156},"sZZnC"," \"@nolag/js-sdk\"",[139,159,160],{"class":149},";\n",[139,162,164],{"class":141,"line":163},2,[139,165,167],{"emptyLinePlaceholder":166},true,"\n",[139,169,171,174,178,181,184,188,191,194,197],{"class":141,"line":170},3,[139,172,173],{"class":145},"const",[139,175,177],{"class":176},"sj4cs"," api",[139,179,180],{"class":145}," =",[139,182,183],{"class":145}," new",[139,185,187],{"class":186},"sScJk"," NoLagApi",[139,189,190],{"class":149},"(process.env.",[139,192,193],{"class":176},"NOLAG_API_KEY",[139,195,196],{"class":149},"); ",[139,198,200],{"class":199},"sJ8bj","// nlg_live_...\n",[139,202,204],{"class":141,"line":203},4,[139,205,167],{"emptyLinePlaceholder":166},[139,207,209],{"class":141,"line":208},5,[139,210,211],{"class":199},"// When a new customer signs up, create a scope for them\n",[139,213,215,218,221,224,227,231,234,237,239,242,244,246],{"class":141,"line":214},6,[139,216,217],{"class":145},"async",[139,219,220],{"class":145}," function",[139,222,223],{"class":186}," onboardTenant",[139,225,226],{"class":149},"(",[139,228,230],{"class":229},"s4XuR","tenantSlug",[139,232,233],{"class":145},":",[139,235,236],{"class":176}," string",[139,238,32],{"class":149},[139,240,241],{"class":229},"tenantName",[139,243,233],{"class":145},[139,245,236],{"class":176},[139,247,248],{"class":149},") {\n",[139,250,252],{"class":141,"line":251},7,[139,253,254],{"class":199},"  // 1. Create the scope\n",[139,256,258,261,264,266,269,272,275],{"class":141,"line":257},8,[139,259,260],{"class":145},"  const",[139,262,263],{"class":176}," scope",[139,265,180],{"class":145},[139,267,268],{"class":145}," await",[139,270,271],{"class":149}," api.scopes.",[139,273,274],{"class":186},"create",[139,276,277],{"class":149},"({ slug: tenantSlug, name: tenantName });\n",[139,279,281],{"class":141,"line":280},9,[139,282,167],{"emptyLinePlaceholder":166},[139,284,286],{"class":141,"line":285},10,[139,287,288],{"class":199},"  // 2. Create an actor for one of this tenant's users and bind it to the scope.\n",[139,290,292],{"class":141,"line":291},11,[139,293,294],{"class":199},"  //    (Over raw REST, POST /v1/actors accepts accessScopeId in the same call.)\n",[139,296,298,300,303,305,307,310,312,315,318,321,324],{"class":141,"line":297},12,[139,299,260],{"class":145},[139,301,302],{"class":176}," actor",[139,304,180],{"class":145},[139,306,268],{"class":145},[139,308,309],{"class":149}," api.actors.",[139,311,274],{"class":186},[139,313,314],{"class":149},"({ name: ",[139,316,317],{"class":156},"\"user-alice\"",[139,319,320],{"class":149},", actorType: ",[139,322,323],{"class":156},"\"user\"",[139,325,326],{"class":149}," });\n",[139,328,330,333,335,338],{"class":141,"line":329},13,[139,331,332],{"class":145},"  await",[139,334,271],{"class":149},[139,336,337],{"class":186},"addActor",[139,339,340],{"class":149},"(scope.accessScopeId, actor.actorTokenId);\n",[139,342,344],{"class":141,"line":343},14,[139,345,167],{"emptyLinePlaceholder":166},[139,347,349],{"class":141,"line":348},15,[139,350,351],{"class":199},"  // 3. Hand actor.accessToken to Alice's client. It is returned once.\n",[139,353,355],{"class":141,"line":354},16,[139,356,167],{"emptyLinePlaceholder":166},[139,358,360],{"class":141,"line":359},17,[139,361,362],{"class":199},"  // Alice's client subscribes to my-app-a3f9/chat/messages as usual.\n",[139,364,366],{"class":141,"line":365},18,[139,367,368],{"class":199},"  // The broker resolves her topics under the tenant slug:\n",[139,370,372],{"class":141,"line":371},19,[139,373,374],{"class":199},"  //   my-app-a3f9/acme-corp/chat/messages\n",[139,376,378],{"class":141,"line":377},20,[139,379,380],{"class":199},"  //   my-app-a3f9/acme-corp/notifications/alerts\n",[139,382,384,387],{"class":141,"line":383},21,[139,385,386],{"class":145},"  return",[139,388,389],{"class":149}," { scope, actor };\n",[139,391,393],{"class":141,"line":392},22,[139,394,395],{"class":149},"}\n",[139,397,399],{"class":141,"line":398},23,[139,400,167],{"emptyLinePlaceholder":166},[139,402,404,407,409,411,414,416,419],{"class":141,"line":403},24,[139,405,406],{"class":145},"await",[139,408,223],{"class":186},[139,410,226],{"class":149},[139,412,413],{"class":156},"\"acme-corp\"",[139,415,32],{"class":149},[139,417,418],{"class":156},"\"Acme Corporation\"",[139,420,421],{"class":149},");\n",[14,423,424,425,428,429,432],{},"Off-boarding runs the other way: unscope or delete the tenant's actors, then delete the scope. ",[21,426,427],{},"DELETE /v1/scopes/:id"," refuses with ",[21,430,431],{},"409"," while any actor is still assigned.",[74,434,436],{"id":435},"pattern-2-scopes-for-environment-isolation","Pattern 2: Scopes for Environment Isolation",[14,438,439],{},"Use scopes to isolate environments within the same project. Staging actors and production actors share the same app configuration but operate in completely separate topic namespaces.",[14,441,442],{},"This is useful during development and testing. You can run integration tests against a staging scope without any risk of interfering with production traffic.",[85,444,88],{"id":445},"when-to-use-1",[90,447,448,451,454],{},[93,449,450],{},"Separating staging, preview, and production traffic",[93,452,453],{},"Running automated tests against a shared project",[93,455,456],{},"Blue-green deployment patterns",[85,458,105],{"id":459},"implementation-1",[126,461,462],{},[129,463,465],{"className":131,"code":464,"filename":133,"language":134,"meta":135,"style":135},"import { NoLagApi } from \"@nolag/js-sdk\";\n\nconst api = new NoLagApi(process.env.NOLAG_API_KEY); // nlg_live_...\n\n// Create scopes for environment isolation\nconst stagingScope = await api.scopes.create({ slug: \"staging\", name: \"Staging\" });\nconst productionScope = await api.scopes.create({ slug: \"production\", name: \"Production\" });\n\n// Assign existing actors to environments\nawait api.actors.update(testActorTokenId, {\n  accessScopeId: stagingScope.accessScopeId,\n});\nawait api.actors.update(prodActorTokenId, {\n  accessScopeId: productionScope.accessScopeId,\n});\n\n// Staging actors resolve to:    my-app-a3f9/staging/room/topic\n// Production actors resolve to: my-app-a3f9/production/room/topic\n// They never interfere with each other.\n",[21,466,467,479,483,503,507,512,541,568,572,577,589,594,599,610,615,619,623,628,633],{"__ignoreMap":135},[139,468,469,471,473,475,477],{"class":141,"line":142},[139,470,146],{"class":145},[139,472,150],{"class":149},[139,474,153],{"class":145},[139,476,157],{"class":156},[139,478,160],{"class":149},[139,480,481],{"class":141,"line":163},[139,482,167],{"emptyLinePlaceholder":166},[139,484,485,487,489,491,493,495,497,499,501],{"class":141,"line":170},[139,486,173],{"class":145},[139,488,177],{"class":176},[139,490,180],{"class":145},[139,492,183],{"class":145},[139,494,187],{"class":186},[139,496,190],{"class":149},[139,498,193],{"class":176},[139,500,196],{"class":149},[139,502,200],{"class":199},[139,504,505],{"class":141,"line":203},[139,506,167],{"emptyLinePlaceholder":166},[139,508,509],{"class":141,"line":208},[139,510,511],{"class":199},"// Create scopes for environment isolation\n",[139,513,514,516,519,521,523,525,527,530,533,536,539],{"class":141,"line":214},[139,515,173],{"class":145},[139,517,518],{"class":176}," stagingScope",[139,520,180],{"class":145},[139,522,268],{"class":145},[139,524,271],{"class":149},[139,526,274],{"class":186},[139,528,529],{"class":149},"({ slug: ",[139,531,532],{"class":156},"\"staging\"",[139,534,535],{"class":149},", name: ",[139,537,538],{"class":156},"\"Staging\"",[139,540,326],{"class":149},[139,542,543,545,548,550,552,554,556,558,561,563,566],{"class":141,"line":251},[139,544,173],{"class":145},[139,546,547],{"class":176}," productionScope",[139,549,180],{"class":145},[139,551,268],{"class":145},[139,553,271],{"class":149},[139,555,274],{"class":186},[139,557,529],{"class":149},[139,559,560],{"class":156},"\"production\"",[139,562,535],{"class":149},[139,564,565],{"class":156},"\"Production\"",[139,567,326],{"class":149},[139,569,570],{"class":141,"line":257},[139,571,167],{"emptyLinePlaceholder":166},[139,573,574],{"class":141,"line":280},[139,575,576],{"class":199},"// Assign existing actors to environments\n",[139,578,579,581,583,586],{"class":141,"line":285},[139,580,406],{"class":145},[139,582,309],{"class":149},[139,584,585],{"class":186},"update",[139,587,588],{"class":149},"(testActorTokenId, {\n",[139,590,591],{"class":141,"line":291},[139,592,593],{"class":149},"  accessScopeId: stagingScope.accessScopeId,\n",[139,595,596],{"class":141,"line":297},[139,597,598],{"class":149},"});\n",[139,600,601,603,605,607],{"class":141,"line":329},[139,602,406],{"class":145},[139,604,309],{"class":149},[139,606,585],{"class":186},[139,608,609],{"class":149},"(prodActorTokenId, {\n",[139,611,612],{"class":141,"line":343},[139,613,614],{"class":149},"  accessScopeId: productionScope.accessScopeId,\n",[139,616,617],{"class":141,"line":348},[139,618,598],{"class":149},[139,620,621],{"class":141,"line":354},[139,622,167],{"emptyLinePlaceholder":166},[139,624,625],{"class":141,"line":359},[139,626,627],{"class":199},"// Staging actors resolve to:    my-app-a3f9/staging/room/topic\n",[139,629,630],{"class":141,"line":365},[139,631,632],{"class":199},"// Production actors resolve to: my-app-a3f9/production/room/topic\n",[139,634,635],{"class":141,"line":371},[139,636,637],{"class":199},"// They never interfere with each other.\n",[14,639,640],{},"A reassigned actor picks up its new scope at its next connection, or within about 10 minutes on a live connection. Reconnect test actors after moving them if the change has to be immediate.",[74,642,644],{"id":643},"pattern-3-scopes-for-team-isolation","Pattern 3: Scopes for Team Isolation",[14,646,647],{},"Use scopes to partition communication by team or department within a single organization. Each team gets its own isolated set of topics while sharing the same app infrastructure.",[85,649,88],{"id":650},"when-to-use-2",[90,652,653,656,659],{},[93,654,655],{},"Enterprise applications with departmental isolation requirements",[93,657,658],{},"Compliance scenarios where teams must not share communication channels",[93,660,661],{},"Internal tools with per-team dashboards and notifications",[85,663,105],{"id":664},"implementation-2",[126,666,667],{},[129,668,670],{"className":131,"code":669,"filename":133,"language":134,"meta":135,"style":135},"import { NoLagApi } from \"@nolag/js-sdk\";\n\nconst api = new NoLagApi(process.env.NOLAG_API_KEY); // nlg_live_...\n\n// Create scopes for team isolation\nconst teams = [\"engineering\", \"sales\", \"support\"];\nconst scopesByTeam: Record\u003Cstring, string> = {};\n\nfor (const team of teams) {\n  const scope = await api.scopes.create({\n    slug: team,\n    name: team.charAt(0).toUpperCase() + team.slice(1),\n  });\n  scopesByTeam[team] = scope.accessScopeId;\n}\n\n// Assign actors to their teams\nawait api.scopes.addActor(scopesByTeam.engineering, engineerActorTokenId);\n\n// Each team gets isolated communication channels:\n//   my-app-a3f9/engineering/standup/messages\n//   my-app-a3f9/sales/pipeline/updates\n//   my-app-a3f9/support/tickets/notifications\n",[21,671,672,684,688,708,712,717,745,776,780,799,816,821,860,865,875,879,883,888,899,903,908,913,918],{"__ignoreMap":135},[139,673,674,676,678,680,682],{"class":141,"line":142},[139,675,146],{"class":145},[139,677,150],{"class":149},[139,679,153],{"class":145},[139,681,157],{"class":156},[139,683,160],{"class":149},[139,685,686],{"class":141,"line":163},[139,687,167],{"emptyLinePlaceholder":166},[139,689,690,692,694,696,698,700,702,704,706],{"class":141,"line":170},[139,691,173],{"class":145},[139,693,177],{"class":176},[139,695,180],{"class":145},[139,697,183],{"class":145},[139,699,187],{"class":186},[139,701,190],{"class":149},[139,703,193],{"class":176},[139,705,196],{"class":149},[139,707,200],{"class":199},[139,709,710],{"class":141,"line":203},[139,711,167],{"emptyLinePlaceholder":166},[139,713,714],{"class":141,"line":208},[139,715,716],{"class":199},"// Create scopes for team isolation\n",[139,718,719,721,724,726,729,732,734,737,739,742],{"class":141,"line":214},[139,720,173],{"class":145},[139,722,723],{"class":176}," teams",[139,725,180],{"class":145},[139,727,728],{"class":149}," [",[139,730,731],{"class":156},"\"engineering\"",[139,733,32],{"class":149},[139,735,736],{"class":156},"\"sales\"",[139,738,32],{"class":149},[139,740,741],{"class":156},"\"support\"",[139,743,744],{"class":149},"];\n",[139,746,747,749,752,754,757,760,763,765,767,770,773],{"class":141,"line":251},[139,748,173],{"class":145},[139,750,751],{"class":176}," scopesByTeam",[139,753,233],{"class":145},[139,755,756],{"class":186}," Record",[139,758,759],{"class":149},"\u003C",[139,761,762],{"class":176},"string",[139,764,32],{"class":149},[139,766,762],{"class":176},[139,768,769],{"class":149},"> ",[139,771,772],{"class":145},"=",[139,774,775],{"class":149}," {};\n",[139,777,778],{"class":141,"line":257},[139,779,167],{"emptyLinePlaceholder":166},[139,781,782,785,788,790,793,796],{"class":141,"line":280},[139,783,784],{"class":145},"for",[139,786,787],{"class":149}," (",[139,789,173],{"class":145},[139,791,792],{"class":176}," team",[139,794,795],{"class":145}," of",[139,797,798],{"class":149}," teams) {\n",[139,800,801,803,805,807,809,811,813],{"class":141,"line":285},[139,802,260],{"class":145},[139,804,263],{"class":176},[139,806,180],{"class":145},[139,808,268],{"class":145},[139,810,271],{"class":149},[139,812,274],{"class":186},[139,814,815],{"class":149},"({\n",[139,817,818],{"class":141,"line":291},[139,819,820],{"class":149},"    slug: team,\n",[139,822,823,826,829,831,834,837,840,843,846,849,852,854,857],{"class":141,"line":297},[139,824,825],{"class":149},"    name: team.",[139,827,828],{"class":186},"charAt",[139,830,226],{"class":149},[139,832,833],{"class":176},"0",[139,835,836],{"class":149},").",[139,838,839],{"class":186},"toUpperCase",[139,841,842],{"class":149},"() ",[139,844,845],{"class":145},"+",[139,847,848],{"class":149}," team.",[139,850,851],{"class":186},"slice",[139,853,226],{"class":149},[139,855,856],{"class":176},"1",[139,858,859],{"class":149},"),\n",[139,861,862],{"class":141,"line":329},[139,863,864],{"class":149},"  });\n",[139,866,867,870,872],{"class":141,"line":343},[139,868,869],{"class":149},"  scopesByTeam[team] ",[139,871,772],{"class":145},[139,873,874],{"class":149}," scope.accessScopeId;\n",[139,876,877],{"class":141,"line":348},[139,878,395],{"class":149},[139,880,881],{"class":141,"line":354},[139,882,167],{"emptyLinePlaceholder":166},[139,884,885],{"class":141,"line":359},[139,886,887],{"class":199},"// Assign actors to their teams\n",[139,889,890,892,894,896],{"class":141,"line":365},[139,891,406],{"class":145},[139,893,271],{"class":149},[139,895,337],{"class":186},[139,897,898],{"class":149},"(scopesByTeam.engineering, engineerActorTokenId);\n",[139,900,901],{"class":141,"line":371},[139,902,167],{"emptyLinePlaceholder":166},[139,904,905],{"class":141,"line":377},[139,906,907],{"class":199},"// Each team gets isolated communication channels:\n",[139,909,910],{"class":141,"line":383},[139,911,912],{"class":199},"//   my-app-a3f9/engineering/standup/messages\n",[139,914,915],{"class":141,"line":392},[139,916,917],{"class":199},"//   my-app-a3f9/sales/pipeline/updates\n",[139,919,920],{"class":141,"line":398},[139,921,922],{"class":199},"//   my-app-a3f9/support/tickets/notifications\n",[74,924,926],{"id":925},"agent-multi-tenancy","Agent Multi-Tenancy",[14,928,929,930,934],{},"Access Scopes are particularly powerful when combined with ",[41,931,933],{"href":932},"/docs/agents/getting-started","AI Agents",". You can deploy the same agent workflow for multiple tenants, with each tenant's agents operating in complete isolation.",[14,936,937,940,941,944,945,948,949,32,952,32,955,32,958,32,961,32,964,120,967,970],{},[21,938,939],{},"@nolag/agents"," coordinates through a workflow room (",[21,942,943],{},"default-workflow",", seeded by the ",[21,946,947],{},"nolag-agents-sdk"," blueprint) with the topics ",[21,950,951],{},"tasks",[21,953,954],{},"results",[21,956,957],{},"state",[21,959,960],{},"events",[21,962,963],{},"inbox",[21,965,966],{},"tools",[21,968,969],{},"approval",". Those are ordinary topics, so they are namespaced under the scope like everything else. An orchestrator in tenant A cannot dispatch tasks to workers in tenant B.",[85,972,105],{"id":973},"implementation-3",[14,975,976,977,980,981,983],{},"Provision one scoped orchestrator and one scoped worker per tenant. ",[21,978,979],{},"my-agents-a3f9"," is the slug returned when you created the app from the ",[21,982,947],{}," blueprint.",[126,985,986],{},[129,987,989],{"className":131,"code":988,"filename":133,"language":134,"meta":135,"style":135},"import { NoLagApi } from \"@nolag/js-sdk\";\n\nconst api = new NoLagApi(process.env.NOLAG_API_KEY); // nlg_live_...\n\n// Multi-tenant agent deployment: each tenant gets its own scoped agents\nasync function deployAgentsForTenant(tenantSlug: string, scopeId: string) {\n  // Create an orchestrator actor scoped to this tenant\n  const orchestrator = await api.actors.create({\n    name: `${tenantSlug}-orchestrator`,\n    actorType: \"orchestrator\",\n  });\n  await api.scopes.addActor(scopeId, orchestrator.actorTokenId);\n\n  // Create a worker actor scoped to this tenant\n  const worker = await api.actors.create({\n    name: `${tenantSlug}-worker`,\n    actorType: \"agent\",\n  });\n  await api.scopes.addActor(scopeId, worker.actorTokenId);\n\n  // Both connect with @nolag/agents against the same app and room. The\n  // broker resolves their coordination topics to the tenant's namespace:\n  //   Acme:      my-agents-a3f9/acme-corp/default-workflow/tasks\n  //   Beta Corp: my-agents-a3f9/beta-corp/default-workflow/tasks\n  return { orchestrator, worker };\n}\n\n// Deploy for each tenant\nawait deployAgentsForTenant(\"acme-corp\", acmeScopeId);\nawait deployAgentsForTenant(\"beta-corp\", betaScopeId);\n",[21,990,991,1003,1007,1027,1031,1036,1064,1069,1086,1102,1112,1116,1127,1131,1136,1153,1166,1175,1179,1190,1194,1199,1204,1209,1214,1222,1227,1232,1238,1252],{"__ignoreMap":135},[139,992,993,995,997,999,1001],{"class":141,"line":142},[139,994,146],{"class":145},[139,996,150],{"class":149},[139,998,153],{"class":145},[139,1000,157],{"class":156},[139,1002,160],{"class":149},[139,1004,1005],{"class":141,"line":163},[139,1006,167],{"emptyLinePlaceholder":166},[139,1008,1009,1011,1013,1015,1017,1019,1021,1023,1025],{"class":141,"line":170},[139,1010,173],{"class":145},[139,1012,177],{"class":176},[139,1014,180],{"class":145},[139,1016,183],{"class":145},[139,1018,187],{"class":186},[139,1020,190],{"class":149},[139,1022,193],{"class":176},[139,1024,196],{"class":149},[139,1026,200],{"class":199},[139,1028,1029],{"class":141,"line":203},[139,1030,167],{"emptyLinePlaceholder":166},[139,1032,1033],{"class":141,"line":208},[139,1034,1035],{"class":199},"// Multi-tenant agent deployment: each tenant gets its own scoped agents\n",[139,1037,1038,1040,1042,1045,1047,1049,1051,1053,1055,1058,1060,1062],{"class":141,"line":214},[139,1039,217],{"class":145},[139,1041,220],{"class":145},[139,1043,1044],{"class":186}," deployAgentsForTenant",[139,1046,226],{"class":149},[139,1048,230],{"class":229},[139,1050,233],{"class":145},[139,1052,236],{"class":176},[139,1054,32],{"class":149},[139,1056,1057],{"class":229},"scopeId",[139,1059,233],{"class":145},[139,1061,236],{"class":176},[139,1063,248],{"class":149},[139,1065,1066],{"class":141,"line":251},[139,1067,1068],{"class":199},"  // Create an orchestrator actor scoped to this tenant\n",[139,1070,1071,1073,1076,1078,1080,1082,1084],{"class":141,"line":257},[139,1072,260],{"class":145},[139,1074,1075],{"class":176}," orchestrator",[139,1077,180],{"class":145},[139,1079,268],{"class":145},[139,1081,309],{"class":149},[139,1083,274],{"class":186},[139,1085,815],{"class":149},[139,1087,1088,1091,1094,1096,1099],{"class":141,"line":280},[139,1089,1090],{"class":149},"    name: ",[139,1092,1093],{"class":156},"`${",[139,1095,230],{"class":149},[139,1097,1098],{"class":156},"}-orchestrator`",[139,1100,1101],{"class":149},",\n",[139,1103,1104,1107,1110],{"class":141,"line":285},[139,1105,1106],{"class":149},"    actorType: ",[139,1108,1109],{"class":156},"\"orchestrator\"",[139,1111,1101],{"class":149},[139,1113,1114],{"class":141,"line":291},[139,1115,864],{"class":149},[139,1117,1118,1120,1122,1124],{"class":141,"line":297},[139,1119,332],{"class":145},[139,1121,271],{"class":149},[139,1123,337],{"class":186},[139,1125,1126],{"class":149},"(scopeId, orchestrator.actorTokenId);\n",[139,1128,1129],{"class":141,"line":329},[139,1130,167],{"emptyLinePlaceholder":166},[139,1132,1133],{"class":141,"line":343},[139,1134,1135],{"class":199},"  // Create a worker actor scoped to this tenant\n",[139,1137,1138,1140,1143,1145,1147,1149,1151],{"class":141,"line":348},[139,1139,260],{"class":145},[139,1141,1142],{"class":176}," worker",[139,1144,180],{"class":145},[139,1146,268],{"class":145},[139,1148,309],{"class":149},[139,1150,274],{"class":186},[139,1152,815],{"class":149},[139,1154,1155,1157,1159,1161,1164],{"class":141,"line":354},[139,1156,1090],{"class":149},[139,1158,1093],{"class":156},[139,1160,230],{"class":149},[139,1162,1163],{"class":156},"}-worker`",[139,1165,1101],{"class":149},[139,1167,1168,1170,1173],{"class":141,"line":359},[139,1169,1106],{"class":149},[139,1171,1172],{"class":156},"\"agent\"",[139,1174,1101],{"class":149},[139,1176,1177],{"class":141,"line":365},[139,1178,864],{"class":149},[139,1180,1181,1183,1185,1187],{"class":141,"line":371},[139,1182,332],{"class":145},[139,1184,271],{"class":149},[139,1186,337],{"class":186},[139,1188,1189],{"class":149},"(scopeId, worker.actorTokenId);\n",[139,1191,1192],{"class":141,"line":377},[139,1193,167],{"emptyLinePlaceholder":166},[139,1195,1196],{"class":141,"line":383},[139,1197,1198],{"class":199},"  // Both connect with @nolag/agents against the same app and room. The\n",[139,1200,1201],{"class":141,"line":392},[139,1202,1203],{"class":199},"  // broker resolves their coordination topics to the tenant's namespace:\n",[139,1205,1206],{"class":141,"line":398},[139,1207,1208],{"class":199},"  //   Acme:      my-agents-a3f9/acme-corp/default-workflow/tasks\n",[139,1210,1211],{"class":141,"line":403},[139,1212,1213],{"class":199},"  //   Beta Corp: my-agents-a3f9/beta-corp/default-workflow/tasks\n",[139,1215,1217,1219],{"class":141,"line":1216},25,[139,1218,386],{"class":145},[139,1220,1221],{"class":149}," { orchestrator, worker };\n",[139,1223,1225],{"class":141,"line":1224},26,[139,1226,395],{"class":149},[139,1228,1230],{"class":141,"line":1229},27,[139,1231,167],{"emptyLinePlaceholder":166},[139,1233,1235],{"class":141,"line":1234},28,[139,1236,1237],{"class":199},"// Deploy for each tenant\n",[139,1239,1241,1243,1245,1247,1249],{"class":141,"line":1240},29,[139,1242,406],{"class":145},[139,1244,1044],{"class":186},[139,1246,226],{"class":149},[139,1248,413],{"class":156},[139,1250,1251],{"class":149},", acmeScopeId);\n",[139,1253,1255,1257,1259,1261,1264],{"class":141,"line":1254},30,[139,1256,406],{"class":145},[139,1258,1044],{"class":186},[139,1260,226],{"class":149},[139,1262,1263],{"class":156},"\"beta-corp\"",[139,1265,1266],{"class":149},", betaScopeId);\n",[14,1268,1269],{},"The agent code itself is the same for every tenant. Only the actor token differs, and the token carries the scope:",[126,1271,1272],{},[129,1273,1275],{"className":131,"code":1274,"filename":133,"language":134,"meta":135,"style":135},"import { NoLag } from \"@nolag/js-sdk\";\nimport { NoLagAgents, Handoff } from \"@nolag/agents\";\n\n// ORCHESTRATOR_TOKEN belongs to an actor scoped to \"acme-corp\"\nconst client = NoLag(ORCHESTRATOR_TOKEN);\nconst agents = new NoLagAgents({ client, appName: APP_SLUG }); // \"my-agents-a3f9\"\nawait client.connect();\nawait agents.ready();\n\nconst room = agents.room(\"default-workflow\");\nconst handoff = new Handoff(room);\n\n// Reaches only workers in the acme-corp scope\nconst result = await handoff.dispatch(\"summarize\", { text: \"...\" }, { waitForResult: true });\nif (result) {\n  console.log(result.payload);\n}\n",[21,1276,1277,1290,1304,1308,1313,1332,1358,1371,1383,1387,1408,1425,1429,1434,1470,1478,1489],{"__ignoreMap":135},[139,1278,1279,1281,1284,1286,1288],{"class":141,"line":142},[139,1280,146],{"class":145},[139,1282,1283],{"class":149}," { NoLag } ",[139,1285,153],{"class":145},[139,1287,157],{"class":156},[139,1289,160],{"class":149},[139,1291,1292,1294,1297,1299,1302],{"class":141,"line":163},[139,1293,146],{"class":145},[139,1295,1296],{"class":149}," { NoLagAgents, Handoff } ",[139,1298,153],{"class":145},[139,1300,1301],{"class":156}," \"@nolag/agents\"",[139,1303,160],{"class":149},[139,1305,1306],{"class":141,"line":170},[139,1307,167],{"emptyLinePlaceholder":166},[139,1309,1310],{"class":141,"line":203},[139,1311,1312],{"class":199},"// ORCHESTRATOR_TOKEN belongs to an actor scoped to \"acme-corp\"\n",[139,1314,1315,1317,1320,1322,1325,1327,1330],{"class":141,"line":208},[139,1316,173],{"class":145},[139,1318,1319],{"class":176}," client",[139,1321,180],{"class":145},[139,1323,1324],{"class":186}," NoLag",[139,1326,226],{"class":149},[139,1328,1329],{"class":176},"ORCHESTRATOR_TOKEN",[139,1331,421],{"class":149},[139,1333,1334,1336,1339,1341,1343,1346,1349,1352,1355],{"class":141,"line":214},[139,1335,173],{"class":145},[139,1337,1338],{"class":176}," agents",[139,1340,180],{"class":145},[139,1342,183],{"class":145},[139,1344,1345],{"class":186}," NoLagAgents",[139,1347,1348],{"class":149},"({ client, appName: ",[139,1350,1351],{"class":176},"APP_SLUG",[139,1353,1354],{"class":149}," }); ",[139,1356,1357],{"class":199},"// \"my-agents-a3f9\"\n",[139,1359,1360,1362,1365,1368],{"class":141,"line":251},[139,1361,406],{"class":145},[139,1363,1364],{"class":149}," client.",[139,1366,1367],{"class":186},"connect",[139,1369,1370],{"class":149},"();\n",[139,1372,1373,1375,1378,1381],{"class":141,"line":257},[139,1374,406],{"class":145},[139,1376,1377],{"class":149}," agents.",[139,1379,1380],{"class":186},"ready",[139,1382,1370],{"class":149},[139,1384,1385],{"class":141,"line":280},[139,1386,167],{"emptyLinePlaceholder":166},[139,1388,1389,1391,1394,1396,1398,1401,1403,1406],{"class":141,"line":285},[139,1390,173],{"class":145},[139,1392,1393],{"class":176}," room",[139,1395,180],{"class":145},[139,1397,1377],{"class":149},[139,1399,1400],{"class":186},"room",[139,1402,226],{"class":149},[139,1404,1405],{"class":156},"\"default-workflow\"",[139,1407,421],{"class":149},[139,1409,1410,1412,1415,1417,1419,1422],{"class":141,"line":291},[139,1411,173],{"class":145},[139,1413,1414],{"class":176}," handoff",[139,1416,180],{"class":145},[139,1418,183],{"class":145},[139,1420,1421],{"class":186}," Handoff",[139,1423,1424],{"class":149},"(room);\n",[139,1426,1427],{"class":141,"line":297},[139,1428,167],{"emptyLinePlaceholder":166},[139,1430,1431],{"class":141,"line":329},[139,1432,1433],{"class":199},"// Reaches only workers in the acme-corp scope\n",[139,1435,1436,1438,1441,1443,1445,1448,1451,1453,1456,1459,1462,1465,1468],{"class":141,"line":343},[139,1437,173],{"class":145},[139,1439,1440],{"class":176}," result",[139,1442,180],{"class":145},[139,1444,268],{"class":145},[139,1446,1447],{"class":149}," handoff.",[139,1449,1450],{"class":186},"dispatch",[139,1452,226],{"class":149},[139,1454,1455],{"class":156},"\"summarize\"",[139,1457,1458],{"class":149},", { text: ",[139,1460,1461],{"class":156},"\"...\"",[139,1463,1464],{"class":149}," }, { waitForResult: ",[139,1466,1467],{"class":176},"true",[139,1469,326],{"class":149},[139,1471,1472,1475],{"class":141,"line":348},[139,1473,1474],{"class":145},"if",[139,1476,1477],{"class":149}," (result) {\n",[139,1479,1480,1483,1486],{"class":141,"line":354},[139,1481,1482],{"class":149},"  console.",[139,1484,1485],{"class":186},"log",[139,1487,1488],{"class":149},"(result.payload);\n",[139,1490,1491],{"class":141,"line":359},[139,1492,395],{"class":149},[47,1494,1495,1500],{"type":49},[14,1496,1497],{},[53,1498,1499],{},"Key Advantage",[14,1501,1502,1503,1506],{},"With Access Scopes, you deploy ",[53,1504,1505],{},"one app, one set of rooms, one set of topics",". The scopes handle isolation automatically. Without scopes, you would need to create a separate app (or even a separate project) for each tenant, duplicating configuration and complicating management.",[74,1508,1510],{"id":1509},"next-steps","Next Steps",[90,1512,1513,1520,1525],{},[93,1514,1515,1519],{},[41,1516,1518],{"href":1517},"/docs/scopes/concepts","Concepts"," - understand how scopes work under the hood",[93,1521,1522,1524],{},[41,1523,44],{"href":43}," - complete endpoint documentation",[93,1526,1527,1531],{},[41,1528,1530],{"href":1529},"/docs/agents/patterns","Agent Patterns"," - coordination patterns that work with scopes",[1533,1534,1535],"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":135,"searchDepth":163,"depth":163,"links":1537},[1538,1542,1546,1550,1553],{"id":76,"depth":163,"text":77,"children":1539},[1540,1541],{"id":87,"depth":170,"text":88},{"id":104,"depth":170,"text":105},{"id":435,"depth":163,"text":436,"children":1543},[1544,1545],{"id":445,"depth":170,"text":88},{"id":459,"depth":170,"text":105},{"id":643,"depth":163,"text":644,"children":1547},[1548,1549],{"id":650,"depth":170,"text":88},{"id":664,"depth":170,"text":105},{"id":925,"depth":163,"text":926,"children":1551},[1552],{"id":973,"depth":170,"text":105},{"id":1509,"depth":163,"text":1510},"Build multi-tenant applications with Access Scopes. Patterns for tenant isolation, environment separation, team partitioning, and agent multi-tenancy.","md",{},"/docs/scopes/multi-tenancy",{"title":5,"description":1554},"docs/scopes/multi-tenancy","neWdQMjVGwDpT1KEq3ifZr8iBt3Sx4WUwsIOVY1NJAU",1789869421216]