From 7a10649e6376cecb7bd016efb19fbbcf35e34a4f Mon Sep 17 00:00:00 2001 From: Christian Bromann Date: Mon, 3 Aug 2026 14:22:04 -0700 Subject: [PATCH 1/2] have component explain different variations --- .../managed-deep-agents-identity.mdx | 31 +- src/langsmith/managed-deep-agents-memory.mdx | 6 +- src/snippets/identity-scope-explorer.jsx | 316 ++++++++++++++++++ 3 files changed, 338 insertions(+), 15 deletions(-) create mode 100644 src/snippets/identity-scope-explorer.jsx diff --git a/src/langsmith/managed-deep-agents-identity.mdx b/src/langsmith/managed-deep-agents-identity.mdx index 9ae4f0323e..b39b12bca4 100644 --- a/src/langsmith/managed-deep-agents-identity.mdx +++ b/src/langsmith/managed-deep-agents-identity.mdx @@ -6,6 +6,7 @@ description: Give each caller their own threads, memory, and credentials so agen import ManagedDeepAgentsPrivateBetaNote from '/snippets/langsmith/managed-deep-agents-private-beta-note.mdx'; import ManagedDeepAgentsTestAndDeploy from '/snippets/langsmith/managed-deep-agents-test-and-deploy.mdx'; +import { IdentityScopeExplorer } from '/snippets/identity-scope-explorer.jsx'; Agents are not anonymous chatbots. As soon as more than one person (or one company) uses a deployment, you need to know: **whose conversation is this, and whose data may the agent see or act on?** Identity lets one deployment serve thousands of users safely, with no data leakage between callers. @@ -98,42 +99,48 @@ The scope values: - **`user`**: downstream calls can act as the signed-in user (for example call GitHub as Alice). - **`agent`**: downstream calls use one shared bot or service token for everyone. +Use the explorer to pick a preset or override each axis. It shows who can access threads, how memory remounts, whose credentials tools use, and the matching declaration: + + + Choose the declaration that matches your product shape: | Product shape | Declaration | Threads | Memory | Credentials | | --- | --- | --- | --- | --- | -| Private assistant or internal tool | `defineIdentity()` | `user` | `user` | `user` | -| Multi-tenant SaaS | `defineIdentity({ scope: "organization" })` | `user` | `organization` | `agent` | -| Shared channel bot | `defineIdentity({ scope: { threads: "conversation" } })` | `conversation` | `user` | `user` | -| Service (cron/webhook, no human caller) | `defineIdentity({ scope: "agent" })` | `user` | `agent` | `agent` | +| Private assistant or internal tool | `defineIdentity({ scope: "user" })` | `user` | `user` | `user` | +| Multi-tenant SaaS | `defineIdentity({ scope: "organization" })` | `user` | `organization` | `none` | +| Shared channel bot | `defineIdentity({ scope: { threads: "conversation", memory: "user", credentials: "agent" } })` | `conversation` | `user` | `agent` | +| Service (cron/webhook) or bare default | `defineIdentity()` or `defineIdentity({ scope: "agent" })` | `user` | `agent` | `agent` | + +Bare `defineIdentity()` expands to the service-agent axes (`threads: "user"`, `memory: "agent"`, `credentials: "agent"`). Unset axes in a per-axis object also follow that agent base, so a bare `{ threads: "conversation" }` inherits shared agent memory unless you set `memory` and `credentials` explicitly. -All declarations default to `backend` auth (your backend asserts the caller) and optional organizations, except `scope: "organization"`, which requires an organization on every request. +All declarations default to `backend` auth (your backend asserts the caller) and optional organizations, except when any axis uses `"organization"`, which requires an organization on every request. **How to choose quickly:** -- One human per conversation who must not see anyone else's data → `defineIdentity()` (the default) +- One human per conversation who must not see anyone else's data → `defineIdentity({ scope: "user" })` - SaaS with customer orgs → `defineIdentity({ scope: "organization" })` -- Shared channel bot → `defineIdentity({ scope: { threads: "conversation" } })` -- Timer or webhook with no user → `defineIdentity({ scope: "agent" })` +- Shared channel bot → set `threads: "conversation"` and keep `memory: "user"` (usually with `credentials: "agent"`) +- Timer, webhook, or shared deployment vault → `defineIdentity()` / `scope: "agent"` ## Add an identity declaration -Create `identity.py` or `identity.ts` next to your agent entry and export a named `identity`. Most projects need no options at all — `defineIdentity()` gives every caller private threads, memory, and credentials behind your backend's auth: +Create `identity.py` or `identity.ts` next to your agent entry and export a named `identity`. For a private assistant, set `scope: "user"` so every caller gets private threads, memory, and credentials behind your backend's auth: ```python identity.py from managed_deepagents import define_identity -identity = define_identity() +identity = define_identity(scope="user") ``` ```ts identity.ts import { defineIdentity } from "managed-deepagents"; -export const identity = defineIdentity(); +export const identity = defineIdentity({ scope: "user" }); ``` @@ -170,7 +177,7 @@ export const identity = defineIdentity({ -Use the full form when you want every field visible, or when you are assembling a config beyond the common shapes. `scope` accepts either one boundary (`"user"`, `"organization"`, `"agent"`, `"none"`) or per-axis overrides such as `{ default: "user", threads: "conversation" }`. +Bare `defineIdentity()` (no options) is the service-agent shape: private threads with shared agent memory and the shared deployment vault. Use the full form when you want every field visible, or when you are assembling a config beyond the common shapes. `scope` accepts either one boundary (`"user"`, `"organization"`, `"agent"`, `"none"`) or per-axis overrides such as `{ default: "user", threads: "conversation" }`. For the full project layout, see the [CLI project file reference](/langsmith/managed-deep-agents-cli#project-file-reference). diff --git a/src/langsmith/managed-deep-agents-memory.mdx b/src/langsmith/managed-deep-agents-memory.mdx index 0336eed99a..a5d51c5633 100644 --- a/src/langsmith/managed-deep-agents-memory.mdx +++ b/src/langsmith/managed-deep-agents-memory.mdx @@ -100,21 +100,21 @@ With identity, `scope.memory` chooses which Hub subdirectory is remounted: Isolation is enforced: a run only sees its remounted tree. Sibling user or organization trees are unreachable. -The default `defineIdentity()` (private assistant shape) sets `memory: "user"`. A service-style `defineIdentity({ scope: "agent" })` uses shared `agent` memory. For more information about scopes and auth, see [Identity](/langsmith/managed-deep-agents-identity). +A private assistant uses `defineIdentity({ scope: "user" })`, which sets `memory: "user"`. Bare `defineIdentity()` (or `scope: "agent"`) uses shared `agent` memory. For more information about scopes and auth, see [Identity](/langsmith/managed-deep-agents-identity). ```python identity.py from managed_deepagents import define_identity -identity = define_identity() +identity = define_identity(scope="user") # scope.memory == "user" ``` ```ts identity.ts import { defineIdentity } from "managed-deepagents"; -export const identity = defineIdentity(); +export const identity = defineIdentity({ scope: "user" }); // scope.memory === "user" ``` diff --git a/src/snippets/identity-scope-explorer.jsx b/src/snippets/identity-scope-explorer.jsx new file mode 100644 index 0000000000..5e7b5ec450 --- /dev/null +++ b/src/snippets/identity-scope-explorer.jsx @@ -0,0 +1,316 @@ +"use client"; + +import { useState } from "react"; + +export const IdentityScopeExplorer = () => { + const [scope, setScope] = useState({ + threads: "user", + memory: "user", + credentials: "user", + }); + const [lang, setLang] = useState("ts"); + + const threadScopes = ["user", "conversation", "organization"]; + const memoryScopes = ["user", "organization", "agent", "none"]; + const credentialScopes = ["user", "agent", "none", "custom"]; + + const scopeAxes = { + user: { threads: "user", memory: "user", credentials: "user" }, + organization: { + threads: "user", + memory: "organization", + credentials: "none", + }, + agent: { threads: "user", memory: "agent", credentials: "agent" }, + none: { threads: "user", memory: "none", credentials: "none" }, + }; + + const presets = [ + { + id: "user", + label: "Private assistant", + description: "Each caller gets private threads, memory, and credentials.", + scope: scopeAxes.user, + }, + { + id: "organization", + label: "Multi-tenant SaaS", + description: "Private threads; memory shared inside each customer org.", + scope: scopeAxes.organization, + }, + { + id: "channel", + label: "Shared channel bot", + description: + "One channel conversation, one thread; keep per-caller memory.", + scope: { + threads: "conversation", + memory: "user", + credentials: "agent", + }, + }, + { + id: "agent", + label: "Service agent", + description: + "Shared deployment memory and vault. Matches bare defineIdentity().", + scope: scopeAxes.agent, + }, + ]; + + const threadBehavior = { + user: "Only the signed-in caller can open or resume their threads. Alice cannot see or continue Bob's conversations.", + conversation: + "Everyone in the same upstream channel conversation (for example a Slack thread) shares one agent thread. Callers without a channel id, such as a browser session, fall back to user-owned threads.", + organization: + "Anyone in the same organization can open or resume those threads. Every request must present an organization id.", + }; + + const memoryBehavior = { + user: "Durable Context Hub memory remounts as memories/ (or memories// when organizations are required). Each caller gets a private slice.", + organization: + "Memory remounts as memories/. Callers in the same org share durable notes; other orgs are unreachable. Every request must present an organization id.", + agent: "Memory remounts as memories/agent. The whole deployment shares one slice.", + none: "/memories/user is not mounted, and hot memory is not injected into the prompt.", + }; + + const credentialBehavior = { + user: "Downstream tool calls can act as the signed-in user. Per-caller Connect-with-X routes mount when a credential chain reads user tokens.", + agent: + "Downstream calls use the shared deployment vault (mda connect / Settings → Integrations), not each caller's personal token.", + none: "No managed credential owner. Connect is not inferred from memory scope.", + custom: + "You supply a credentials resolver or endpoint. Tools call runtime.credentials.for(target) to fetch headers for each platform.", + }; + + const scopesEqual = (a, b) => + a.threads === b.threads && + a.memory === b.memory && + a.credentials === b.credentials; + + const activePreset = presets.find((preset) => scopesEqual(scope, preset.scope)); + const namedScope = Object.keys(scopeAxes).find((name) => + scopesEqual(scope, scopeAxes[name]) + ); + const needsOrg = [scope.threads, scope.memory, scope.credentials].includes( + "organization" + ); + const needsExplicitOrg = needsOrg && !namedScope; + + const isCustomCredentials = scope.credentials === "custom"; + + // When credentials are custom, the resolver implies custom mode — omit the + // credentials axis from scope and show a demo resolve function instead. + const formatScopeObject = isCustomCredentials + ? lang === "python" + ? `{\n "threads": "${scope.threads}",\n "memory": "${scope.memory}",\n }` + : `{\n threads: "${scope.threads}",\n memory: "${scope.memory}",\n }` + : lang === "python" + ? `{\n "threads": "${scope.threads}",\n "memory": "${scope.memory}",\n "credentials": "${scope.credentials}",\n }` + : `{\n threads: "${scope.threads}",\n memory: "${scope.memory}",\n credentials: "${scope.credentials}",\n }`; + + const pythonCredentialsBlock = `credentials={ + "github": { + "resolve": resolve_github, + }, + },`; + + const tsCredentialsBlock = `credentials: { + github: { + async resolve({ identity }) { + const credential = await getAccessToken(identity.user.id); + if (!credential) { + throw new Error("Connect the provider before using its tools."); + } + return { + headers: { Authorization: \`Bearer \${credential.token}\` }, + expiresAt: credential.expiresAt.toISOString(), + }; + }, + }, + },`; + + let code; + if (lang === "python") { + if (isCustomCredentials) { + const orgLine = needsExplicitOrg ? '\n organization="required",' : ""; + code = `from managed_deepagents import define_identity + +# Application code: look up and refresh the caller's grant. +def resolve_github(args): + identity = args["identity"] + credential = get_access_token(identity["user"]["id"]) + if not credential: + raise ValueError("Connect the provider before using its tools.") + return { + "headers": {"Authorization": f"Bearer {credential['token']}"}, + "expires_at": credential["expires_at"], + } + +identity = define_identity( + scope=${formatScopeObject},${orgLine} + ${pythonCredentialsBlock} +)`; + } else if (namedScope === "agent") { + code = `from managed_deepagents import define_identity\n\nidentity = define_identity()\n# threads=user, memory=agent, credentials=agent`; + } else if (namedScope) { + code = `from managed_deepagents import define_identity\n\nidentity = define_identity(scope="${namedScope}")`; + } else { + const orgLine = needsExplicitOrg ? '\n organization="required",' : ""; + code = `from managed_deepagents import define_identity\n\nidentity = define_identity(\n scope=${formatScopeObject},${orgLine}\n)`; + } + } else if (isCustomCredentials) { + const orgLine = needsExplicitOrg ? `\n organization: "required",` : ""; + code = `import { defineIdentity } from "managed-deepagents"; +import { getAccessToken } from "./credentials.js"; + +export const identity = defineIdentity({ + scope: ${formatScopeObject},${orgLine} + ${tsCredentialsBlock} +});`; + } else if (namedScope === "agent") { + code = `import { defineIdentity } from "managed-deepagents";\n\nexport const identity = defineIdentity();\n// threads: user, memory: agent, credentials: agent`; + } else if (namedScope) { + code = `import { defineIdentity } from "managed-deepagents";\n\nexport const identity = defineIdentity({ scope: "${namedScope}" });`; + } else { + const orgLine = needsExplicitOrg ? `\n organization: "required",` : ""; + code = `import { defineIdentity } from "managed-deepagents";\n\nexport const identity = defineIdentity({\n scope: ${formatScopeObject},${orgLine}\n});`; + } + + const updateAxis = (axis, value) => { + setScope((current) => ({ ...current, [axis]: value })); + }; + + const axisSelect = (label, axis, options) => ( + + ); + + return ( +
+
+
+ Explore identity scope +
+

+ Pick a product preset or override each axis to see who can access + threads, how memory remounts, and whose credentials tools use. +

+
+ +
+ {presets.map((preset) => { + const selected = activePreset?.id === preset.id; + return ( + + ); + })} +
+ +
+ {axisSelect("Threads", "threads", threadScopes)} + {axisSelect("Memory", "memory", memoryScopes)} + {axisSelect("Credentials", "credentials", credentialScopes)} +
+ +
+
+ How the agent behaves +
+ {activePreset && ( +

+ {activePreset.label}: {activePreset.description} +

+ )} +
    +
  • + + Threads ({scope.threads}): + {" "} + {threadBehavior[scope.threads]} +
  • +
  • + + Memory ({scope.memory}): + {" "} + {memoryBehavior[scope.memory]} +
  • +
  • + + Credentials ({scope.credentials}): + {" "} + {credentialBehavior[scope.credentials]} +
  • +
+ {needsOrg && ( +

+ Organization is required on every request because at least one axis + uses organization scope. +

+ )} + {scope.credentials === "custom" && ( +

+ Custom credentials also need a credentials resolver or endpoint in + the identity declaration. Setting the axis alone is not enough. +

+ )} +
+ +
+
+
+ Generated declaration +
+
+ {[ + { id: "ts", label: "TypeScript" }, + { id: "python", label: "Python" }, + ].map((option) => ( + + ))} +
+
+
+          {code}
+        
+
+
+ ); +}; From 0eee764c762ce2d533c586ea0f160845f9e66f96 Mon Sep 17 00:00:00 2001 From: Christian Bromann Date: Mon, 3 Aug 2026 14:36:13 -0700 Subject: [PATCH 2/2] cr --- src/snippets/identity-scope-explorer.jsx | 29 ++++++++++++++---------- 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/src/snippets/identity-scope-explorer.jsx b/src/snippets/identity-scope-explorer.jsx index 5e7b5ec450..7126d797b7 100644 --- a/src/snippets/identity-scope-explorer.jsx +++ b/src/snippets/identity-scope-explorer.jsx @@ -67,20 +67,21 @@ export const IdentityScopeExplorer = () => { }; const memoryBehavior = { - user: "Durable Context Hub memory remounts as memories/ (or memories// when organizations are required). Each caller gets a private slice.", + user: "The agent keeps a private long-term memory for each caller. Preferences Alice saves never appear when Bob chats. If organizations are required, that private memory is also namespaced per customer org.", organization: - "Memory remounts as memories/. Callers in the same org share durable notes; other orgs are unreachable. Every request must present an organization id.", - agent: "Memory remounts as memories/agent. The whole deployment shares one slice.", - none: "/memories/user is not mounted, and hot memory is not injected into the prompt.", + "Everyone in the same customer org shares one long-term memory. Notes written for Acme stay invisible to Globex. Every request must present an organization id.", + agent: + "The whole deployment shares one long-term memory. Anything the agent learns from one caller can surface for the next, which is fine for a single-tenant bot and risky for multi-user products.", + none: "The agent has no durable memory between runs. It only sees the current conversation, so it cannot recall preferences from earlier chats.", }; const credentialBehavior = { - user: "Downstream tool calls can act as the signed-in user. Per-caller Connect-with-X routes mount when a credential chain reads user tokens.", + user: "When the agent calls external APIs (for example GitHub), it can act as the signed-in person rather than as a shared bot account. Each caller connects their own account.", agent: - "Downstream calls use the shared deployment vault (mda connect / Settings → Integrations), not each caller's personal token.", - none: "No managed credential owner. Connect is not inferred from memory scope.", + "External API calls use one shared bot or service token for everyone (the deployment vault from mda connect / Settings → Integrations).", + none: "Managed Deep Agents does not attach downstream credentials. Tools that need external tokens must obtain them some other way.", custom: - "You supply a credentials resolver or endpoint. Tools call runtime.credentials.for(target) to fetch headers for each platform.", + "You decide how tokens are fetched per caller. The generated example shows a resolve function your backend uses to look up each person's grant before a tool call.", }; const scopesEqual = (a, b) => @@ -136,7 +137,9 @@ export const IdentityScopeExplorer = () => { const orgLine = needsExplicitOrg ? '\n organization="required",' : ""; code = `from managed_deepagents import define_identity -# Application code: look up and refresh the caller's grant. +# from credentials import get_access_token # your server-side lookup + + def resolve_github(args): identity = args["identity"] credential = get_access_token(identity["user"]["id"]) @@ -147,6 +150,7 @@ def resolve_github(args): "expires_at": credential["expires_at"], } + identity = define_identity( scope=${formatScopeObject},${orgLine} ${pythonCredentialsBlock} @@ -274,10 +278,11 @@ export const identity = defineIdentity({ uses organization scope.

)} - {scope.credentials === "custom" && ( + {isCustomCredentials && (

- Custom credentials also need a credentials resolver or endpoint in - the identity declaration. Setting the axis alone is not enough. + Custom mode is implied by a credentials resolver. The generated + example includes a demo resolve function; replace getAccessToken + with your server-side credential lookup.

)}