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) => (
+
+
+ {label}
+
+ updateAxis(axis, event.target.value)}
+ value={scope[axis]}
+ >
+ {options.map((option) => (
+
+ {option}
+
+ ))}
+
+
+ );
+
+ 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 (
+ setScope(preset.scope)}
+ title={preset.description}
+ type="button"
+ >
+ {preset.label}
+
+ );
+ })}
+
+
+
+ {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) => (
+ setLang(option.id)}
+ type="button"
+ >
+ {option.label}
+
+ ))}
+
+
+
+ {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.
)}