From deafc2f94dbd3bc489276d992b83124ba7ee9b54 Mon Sep 17 00:00:00 2001 From: Christian Bromann Date: Wed, 22 Jul 2026 10:18:43 -0700 Subject: [PATCH 1/2] fix(MDA): docs cleanup and improvements --- src/langsmith/managed-deep-agents-connectors/github.mdx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/langsmith/managed-deep-agents-connectors/github.mdx b/src/langsmith/managed-deep-agents-connectors/github.mdx index 0a30f071fa..1d9fa75621 100644 --- a/src/langsmith/managed-deep-agents-connectors/github.mdx +++ b/src/langsmith/managed-deep-agents-connectors/github.mdx @@ -66,7 +66,6 @@ The connector clones each repository when the sandbox is created. On reuse, `on_ | Option (Python / TypeScript) | Default | Purpose | | --- | --- | --- | | `repositories` | `[]` | Repository checkouts and their sandbox paths. | -| `allowlist` | `[]` | Allowed repositories as `owner/repo` or `owner/*`. | | `install_cli` / `installCLI` | `true` | Install the GitHub CLI in the sandbox. | | `inject_credentials` / `injectCredentials` | `true` | Expose resolved GitHub credentials to `git` and `gh`. | From 7c00e1fd7d2d65a8419bce087087d03058818a4e Mon Sep 17 00:00:00 2001 From: Christian Bromann Date: Tue, 28 Jul 2026 20:26:57 -0700 Subject: [PATCH 2/2] more updates --- src/docs.json | 1 + .../managed-deep-agents-channels/github.mdx | 18 +- .../managed-deep-agents-channels/index.mdx | 10 +- .../managed-deep-agents-channels/slack.mdx | 169 +++----- src/langsmith/managed-deep-agents-cli.mdx | 30 +- .../managed-deep-agents-connectors/github.mdx | 60 ++- .../managed-deep-agents-connectors/index.mdx | 8 +- .../integrations.mdx | 119 ++++++ .../langsmith.mdx | 166 ++++---- .../managed-deep-agents-connectors/mcp.mdx | 20 +- src/langsmith/managed-deep-agents-deploy.mdx | 13 +- src/langsmith/managed-deep-agents-evals.mdx | 9 +- .../managed-deep-agents-how-it-works.mdx | 2 +- .../managed-deep-agents-identity.mdx | 383 ++++++++++-------- src/langsmith/managed-deep-agents-memory.mdx | 38 +- .../managed-deep-agents-quickstart.mdx | 4 +- src/langsmith/managed-deep-agents-tools.mdx | 2 +- 17 files changed, 606 insertions(+), 446 deletions(-) create mode 100644 src/langsmith/managed-deep-agents-connectors/integrations.mdx diff --git a/src/docs.json b/src/docs.json index 15568b943d..f83a692b5f 100644 --- a/src/docs.json +++ b/src/docs.json @@ -1662,6 +1662,7 @@ "group": "Connectors", "pages": [ "langsmith/managed-deep-agents-connectors/index", + "langsmith/managed-deep-agents-connectors/integrations", "langsmith/managed-deep-agents-connectors/mcp", "langsmith/managed-deep-agents-connectors/github", "langsmith/managed-deep-agents-connectors/langsmith" diff --git a/src/langsmith/managed-deep-agents-channels/github.mdx b/src/langsmith/managed-deep-agents-channels/github.mdx index 40d5f8ed12..33e5726a1a 100644 --- a/src/langsmith/managed-deep-agents-channels/github.mdx +++ b/src/langsmith/managed-deep-agents-channels/github.mdx @@ -25,16 +25,16 @@ This page covers the **channel** (conversation ingress/egress). Use the [GitHub ## Add a GitHub channel -Add `channels/github.py` or `channels/github.ts` next to your agent entry. The file name becomes the channel name (`github` → `POST /channels/github/events`). Export a named `channel` created with `define_github_channel` / `defineGitHubChannel`. +Add `channels/github.py` or `channels/github.ts` next to your agent entry. The file name becomes the channel name (`github` → `POST /channels/github/events`). Export a named `channel` created with `channels.github`: Handlers are ordered: the first match for a delivery wins. Each handler needs `on` and a `prompt` callback that builds the **human message** for that turn. The agent system prompt remains `instructions.md`. ```python channels/github.py -from managed_deepagents.channels.github import define_github_channel +from managed_deepagents import channels -channel = define_github_channel( +channel = channels.github( handlers=[ { "on": "pull_request.opened", @@ -52,9 +52,9 @@ channel = define_github_channel( ```ts channels/github.ts import type { PullRequestOpenedEvent } from "@octokit/webhooks-types"; -import { defineGitHubChannel } from "managed-deepagents/channels/github"; +import { channels } from "managed-deepagents"; -export const channel = defineGitHubChannel({ +export const channel = channels.github({ handlers: [ { on: "pull_request.opened", @@ -72,7 +72,7 @@ export const channel = defineGitHubChannel({ -Pair with a shared-bot (or equivalent) identity for channel-only installs. The channel actor is the installation/service principal `github-app:`, not the pull request author. Replies use the App installation token—Connect-with-GitHub OAuth is not required for this path. +Pair with a conversation-scoped (or equivalent) identity for channel-only installs. The channel user is the installation/service principal `github-app:`, not the pull request author. Replies use the App installation token—Connect-with-GitHub OAuth is not required for this path. ### Event filters (`on`) @@ -111,7 +111,7 @@ flowchart LR 1. GitHub POSTs to `https:///channels/github/events` (the file stem `github` becomes the path segment). 2. The runtime verifies `X-Hub-Signature-256` against `GITHUB_WEBHOOK_SECRET`, dedupes on `X-GitHub-Delivery`, and returns HTTP 202. -3. It picks the first matching handler, calls `prompt` to build the inbound text, then invokes the graph over trusted loopback with actor and source-thread identity (`source.provider: "github"`). +3. It picks the first matching handler, calls `prompt` to build the inbound text, then invokes the graph over trusted loopback with user and source-thread identity (`source.provider: "github"`). 4. When the matched handler has `autoReply` enabled and the conversation address is `owner/repo#N`, it posts the agent response as an issue/PR comment with the App installation token. Events without an issue/PR number skip the comment even when `autoReply` is `true`. LangGraph auth is bypassed only on `POST /channels/{name}/events` so GitHub can deliver without an ingress secret; the loopback invoke still uses `MDA_INGRESS_SECRET`. @@ -145,7 +145,7 @@ Put these in the project `.env` (or LangSmith workspace secrets) before `mda dep | `GITHUB_APP_ID` | Yes | App id for JWT minting | | `GITHUB_APP_PRIVATE_KEY` | Yes | PEM private key for the App | | `GITHUB_INSTALLATION_ID` | Yes | Installation the channel acts as (single-install) | -| `MDA_INGRESS_SECRET` | Yes when identity uses trusted loopback / `trusted_backend` | Trusted invoke from the Events path into the graph | +| `MDA_INGRESS_SECRET` | Yes when identity uses trusted loopback / `backend` auth | Trusted invoke from the Events path into the graph | ## Configure the GitHub App @@ -184,7 +184,7 @@ Put these in the project `.env` (or LangSmith workspace secrets) before `mda dep Add a Slack Events channel alongside GitHub. - Choose identity presets for channel callers. + Choose an identity scope for channel callers. Route secrets and deploy the channel-enabled agent. diff --git a/src/langsmith/managed-deep-agents-channels/index.mdx b/src/langsmith/managed-deep-agents-channels/index.mdx index 5832176dac..751f921428 100644 --- a/src/langsmith/managed-deep-agents-channels/index.mdx +++ b/src/langsmith/managed-deep-agents-channels/index.mdx @@ -28,7 +28,7 @@ For the full project layout, see the [CLI project file reference](/langsmith/man ## How channels work -1. You declare a channel under `channels/` (for example `defineSlackChannel` / `defineGitHubChannel`). +1. You declare a channel under `channels/` (for example `channels.slack(...)` / `channels.github(...)`). 2. Compile and deploy discover the file name as the channel name (`channels/slack.ts` → `slack`). 3. The runtime mounts provider ingress for that channel on the Agent Server (`POST /channels/{name}/events`). 4. Inbound messages invoke your agent with [identity](/langsmith/managed-deep-agents-identity) stamps so tools and memory see the same caller model as HTTP runs. @@ -40,8 +40,8 @@ Channels require a root identity declaration. Provider-specific delivery details | Pattern | Identity approach | Thread behavior | | --- | --- | --- | -| Shared workspace bot | `shared-bot` preset (`threads: "channel"`) | Conversations are scoped by provider source thread (for example Slack `slack:T…:U…` or GitHub `github-app:`). | -| Linked web + Slack | `validated_token` (for example Supabase/guest) + Connect-with-Slack | Unlinked Slack users get a connect prompt; linked users run as the web actor so browser and Slack share history when `threads: "actor"`. | +| Shared workspace bot | `defineIdentity({ scope: { threads: "conversation" } })` | Conversations are scoped by provider source thread (for example Slack `slack:T…:U…` or GitHub `github-app:`). | +| Linked web + Slack | Validated-token auth (for example Supabase/guest) + Connect-with-Slack | Unlinked Slack users get a connect prompt; linked users run as the web user so browser and Slack share history when `threads` is `"user"`. | The GitHub channel uses an installation/service actor and does not require Connect-with-GitHub. For Slack app setup, secrets, Event Subscriptions, and Connect-with-Slack, see [Slack](/langsmith/managed-deep-agents-channels/slack). For GitHub App webhooks, see [GitHub](/langsmith/managed-deep-agents-channels/github). @@ -49,7 +49,7 @@ The GitHub channel uses an installation/service actor and does not require Conne -When `channels/` is present, `mda deploy` preflights secrets listed in each compiled channel manifest’s `requiredEnv` (for example Slack’s signing secret and bot token, or GitHub App webhook/App credentials) before upload. Missing secrets fail the deploy early. +When `channels/` is present, `mda deploy` provisions what each channel needs before upload: a Slack channel gets its Slack app created and installed through the workspace's Slack connection in LangSmith (see [Slack](/langsmith/managed-deep-agents-channels/slack#the-slack-app)), while a GitHub channel preflights the secrets listed in its compiled manifest’s `requiredEnv` (GitHub App webhook/App credentials). Missing GitHub secrets fail the deploy early. ## Next steps @@ -61,7 +61,7 @@ When `channels/` is present, `mda deploy` preflights secrets listed in each comp Declare a GitHub App webhook channel with handlers for any event. - Choose `shared-bot` or linked `validated_token` for channel callers. + Choose conversation-scoped threads or linked validated-token auth for channel callers. Look up `channels/` project file rules and deploy preflight. diff --git a/src/langsmith/managed-deep-agents-channels/slack.mdx b/src/langsmith/managed-deep-agents-channels/slack.mdx index ad8628882c..c36b3454c3 100644 --- a/src/langsmith/managed-deep-agents-channels/slack.mdx +++ b/src/langsmith/managed-deep-agents-channels/slack.mdx @@ -1,13 +1,13 @@ --- title: Add a Slack channel to Managed Deep Agents sidebarTitle: Slack -description: Declare a Slack Events channel, configure the Slack app, and optionally link Slack users to web actors with Connect-with-Slack. +description: Declare a Slack Events channel; mda deploy creates and installs the Slack app through your workspace's Slack connection in LangSmith. --- import ManagedDeepAgentsPrivateBetaNote from '/snippets/langsmith/managed-deep-agents-private-beta-note.mdx'; import ManagedDeepAgentsTestAndDeploy from '/snippets/langsmith/managed-deep-agents-test-and-deploy.mdx'; -The Slack channel lets workspace members talk to your Managed Deep Agent from Slack. You declare triggers under `channels/`, point the Slack app Events Request URL at your deployment, and the runtime verifies signatures, runs the agent, and can auto-reply in the same thread or DM. +The Slack channel lets workspace members talk to your Managed Deep Agent from Slack. You declare triggers under `channels/`, and `mda deploy` creates and installs the Slack app through your workspace's Slack connection in LangSmith—there is no app to make by hand and no bot token or signing secret to copy. The runtime verifies signatures, runs the agent, and can auto-reply in the same thread or DM. @@ -18,19 +18,19 @@ For the channel model and current limits, see [Channels](/langsmith/managed-deep ## Prerequisites - A Managed Deep Agents project with a root [identity](/langsmith/managed-deep-agents-identity) declaration (`channels/` requires identity). -- A [Slack app](https://api.slack.com/apps) you can install into a workspace. -- Deploy or local Agent Server URL for Event Subscriptions (after first deploy, copy it from the LangSmith deployment dashboard). +- Slack connected for your LangSmith workspace (**Settings → Integrations**). `mda deploy` provisions the channel's Slack app through that connection. +- `MDA_TRIGGER_SERVER_URL` set to the trigger server origin so deploy can reach the provisioning API. ## Add a Slack channel -Add `channels/slack.py` or `channels/slack.ts` next to your agent entry. The file name becomes the channel name (`slack` → `POST /channels/slack/events`). Export a named `channel` created with `define_slack_channel` / `defineSlackChannel`. +Add `channels/slack.py` or `channels/slack.ts` next to your agent entry. The file name becomes the channel name (`slack` → `POST /channels/slack/events`). Export a named `channel` created with `channels.slack`: ```python channels/slack.py -from managed_deepagents.channels.slack import define_slack_channel +from managed_deepagents import channels -channel = define_slack_channel( +channel = channels.slack( on=["app_mention", "direct_message", "thread_reply"], auto_reply=True, mention_behavior="strip", @@ -38,9 +38,9 @@ channel = define_slack_channel( ``` ```ts channels/slack.ts -import { defineSlackChannel } from "managed-deepagents/channels/slack"; +import { channels } from "managed-deepagents"; -export const channel = defineSlackChannel({ +export const channel = channels.slack({ on: ["app_mention", "direct_message", "thread_reply"], autoReply: true, mentionBehavior: "strip", @@ -49,27 +49,29 @@ export const channel = defineSlackChannel({ -Pair this with an identity preset that matches your product: +Pair this with an identity declaration that matches your product: ```python identity.py from managed_deepagents import define_identity -# Shared Slack bot: conversations scoped by Slack source thread -identity = define_identity.preset("shared-bot") +# Shared Slack bot: one Slack conversation maps to one thread +identity = define_identity(scope={"threads": "conversation"}) ``` ```ts identity.ts import { defineIdentity } from "managed-deepagents"; -// Shared Slack bot: conversations scoped by Slack source thread -export const identity = defineIdentity.preset("shared-bot"); +// Shared Slack bot: one Slack conversation maps to one thread +export const identity = defineIdentity({ + scope: { threads: "conversation" }, +}); ``` -For browser + Slack account linking (same actor across web and Slack), use `validated_token` ingress and [Connect-with-Slack](#optional-connect-with-slack) instead of a bare `shared-bot` install. +For browser + Slack account linking (same user across web and Slack), use [validated-token auth](/langsmith/managed-deep-agents-identity#validated-token-browser-direct) with user-owned threads and [Connect-with-Slack](#optional-connect-with-slack) instead of a bare shared bot install. ## How Slack Events work @@ -88,7 +90,7 @@ flowchart LR 1. Slack POSTs to `https:///channels/slack/events` (the file stem `slack` becomes the path segment). 2. The runtime verifies the Slack signing secret against the raw body and returns HTTP 200 within Slack’s ack window. -3. In the background it invokes the graph over trusted loopback, stamping actor and source-thread identity (`source.provider: "slack"`). +3. In the background it invokes the graph over trusted loopback, stamping user and source-thread identity (`source.provider: "slack"`). 4. When `autoReply` is enabled, it posts the agent response back with the Slack Web API (and can set assistant loading status while the run is in progress). LangGraph auth is bypassed only on `POST /channels/{name}/events` so Slack can deliver without an ingress secret; the loopback invoke still uses `MDA_INGRESS_SECRET`. @@ -102,115 +104,69 @@ LangGraph auth is bypassed only on `POST /channels/{name}/events` so Slack can d | `mention_behavior` / `mentionBehavior` | `"strip"` | `"strip"` removes the bot `@mention` from the model input; `"preserve"` keeps it | | `conversation.app_mention` / `conversation.appMention` | `"thread"` | How `@mentions` map to agent threads: `thread`, `conversation`, or `message` | | `conversation.direct_message` / `conversation.directMessage` | `"conversation"` | How DMs map to agent threads | -| `filters` | shared conversations off | Optional include/exclude lists for conversations and actors (`slack:T…:U…`). Slack Connect shared conversations are not supported (`allow_shared_conversations: true` is rejected) | +| `filters` | shared conversations off | Optional include/exclude lists for conversations and users (`slack:T…:U…`). Slack Connect shared conversations are not supported (`allow_shared_conversations: true` is rejected) | +| `app` | deployment name | Optional branding for the Slack app deploy creates: `name`, `description`, `icon` (project-relative path to a 512×512 PNG), `background_color` / `backgroundColor` (`"#RRGGBB"`) | ### Triggers and Slack bot events -| Trigger | When it fires | Subscribe to bot events | Typical bot scopes | +| Trigger | When it fires | Bot events deploy subscribes | Typical bot scopes | | --- | --- | --- | --- | | `app_mention` | Someone `@mentions` the bot in a channel | `app_mention` | `app_mentions:read`, `chat:write` | | `direct_message` | Someone DMs the bot | `message.im` | `im:history`, `chat:write` | | `thread_reply` | Someone replies in a thread the bot already joined (no new mention required) | `message.channels`, `message.groups` | `channels:history`, `groups:history`, `chat:write` | -`mda` derives required OAuth scopes from the `on` list at compile time. After you change scopes in the Slack app, **reinstall the app** to the workspace so the new scopes apply. - -## Required secrets - -Put these in the project `.env` (or LangSmith workspace secrets) before `mda deploy`. Deploy preflights the Slack pair when `channels/` is present. - -| Variable | Required | Role | -| --- | --- | --- | -| `SLACK_SIGNING_SECRET` | Yes | Verifies Slack Events signatures (HMAC) | -| `SLACK_BOT_TOKEN` | Yes | Slack Web API for auto-reply and assistant status | -| `MDA_INGRESS_SECRET` | Yes when identity uses trusted loopback / `trusted_backend` | Trusted invoke from the Events path into the graph | -| `SLACK_CLIENT_ID` / `SLACK_CLIENT_SECRET` | Optional | Connect-with-Slack OIDC | -| `MDA_PUBLIC_APP_URL` | Optional (required for Connect-with-Slack) | Browser UI origin shown in connect prompts and post-OAuth return | -| `MDA_PUBLIC_API_URL` | Optional (recommended on Host) | Public Agent Server URL used as Slack OAuth `redirect_uri` | -| `MDA_GUEST_SIGNING_KEY` | Optional (required for Connect-with-Slack / guest) | Signs guest tokens and OAuth state | - -Optional install pins for tests or multi-install hardening: `SLACK_API_APP_ID`, `SLACK_TEAM_ID`, `SLACK_BOT_USER_ID`. - -## Configure the Slack app +The app's event subscriptions follow your `on` list: `thread_reply` is what asks for the channel message events it needs. `mda deploy` pushes the subscriptions (and OAuth scopes) to the app on every deploy, so changing `on` and redeploying is the whole update—there is no reinstall step. -Create or open a Slack app at [api.slack.com/apps](https://api.slack.com/apps), then wire Event Subscriptions and OAuth to your Agent Server. +## The Slack app -### 1. Create the app and install it +A Slack channel needs a Slack app, but the app is not something your project supplies. `mda deploy` creates and installs one through the Slack connection your workspace configured in LangSmith: -1. Create an app **from scratch** in the workspace you will use for testing. -2. Under **OAuth & Permissions**, add the [bot token scopes](#triggers-and-slack-bot-events) that match your `on` triggers (at minimum `chat:write` plus the history/mention scopes above). -3. Install the app to the workspace and copy the **Bot User OAuth Token** into `SLACK_BOT_TOKEN`. -4. Under **Basic Information**, copy the **Signing Secret** into `SLACK_SIGNING_SECRET`. +1. Creates the app (branded with your `app` config, or the deployment name), installs it into the connected workspace, and reinstalls it when scopes change. +2. Points its Events Request URL at `https:///channels/slack/events` and subscribes it to the bot events your `on` triggers need. +3. Writes the bot token, signing secret, app id, team id, and bot user id onto the deployment as secrets. - - Slack App Credentials section showing App ID, Client ID, masked Client Secret, and masked Signing Secret - +Those keys—`SLACK_BOT_TOKEN`, `SLACK_SIGNING_SECRET`, `SLACK_API_APP_ID`, `SLACK_TEAM_ID`, `SLACK_BOT_USER_ID`—are **deploy-owned**. Deploy writes and overwrites them, and a value left in `.env` no longer shadows the real connection, so there is nothing Slack-specific to author before the first deploy. -Copy the **Signing Secret** into `SLACK_SIGNING_SECRET`. For [Connect-with-Slack](#optional-connect-with-slack), also copy **Client ID** into `SLACK_CLIENT_ID` and **Client Secret** into `SLACK_CLIENT_SECRET`. Prefer the Signing Secret over the deprecated Verification Token. - -### 2. Point Event Subscriptions at your deployment - -Deploy the agent first (or run `mda dev`) so the Events URL exists, then enable Event Subscriptions: - -| Setting | Value | -| --- | --- | -| Enable Events | On | -| Request URL | `https:///channels/slack/events` | +Two limits follow from how Slack apps work: -Replace `` with the Agent Server URL from `mda deploy` / the LangSmith deployment dashboard (for local dev, use your publicly reachable tunnel or equivalent—Slack must reach the URL). +- **One Slack app per deployment**, so a project may declare at most one Slack channel. +- Slack needs a public Events URL, which a first deploy only learns at the end: the first `mda deploy` warns and skips the app, and the **next deploy connects it**. Re-running deploy on an existing deployment connects or updates the same app instead of making another. -Slack sends a `url_verification` challenge; the managed runtime responds automatically when the signing secret matches. - -### 3. Subscribe to bot events - -Under **Subscribe to bot events**, add every event your triggers need: - -- `app_mention` -- `message.im` (for `direct_message`) -- `message.channels` and `message.groups` (for `thread_reply`) - -Invite the bot to each channel where you will `@mention` it. Add `message.groups` when the bot should continue threads in private channels (not shown in the example below). - - - Slack Event Subscriptions page showing Enable Events on, a verified Request URL ending in /channels/slack/events, and bot events app_mention, message.channels, and message.im - - -### 4. Confirm bot token scopes +## Required secrets -Under **OAuth & Permissions → Bot Token Scopes**, confirm scopes match the table above. If you add scopes after the first install, reinstall the app, then re-invite the bot to channels. Add `groups:history` when the bot should continue threads in private channels (not shown in the example below). +| Variable | Required | Role | +| --- | --- | --- | +| `MDA_TRIGGER_SERVER_URL` | Yes | Trigger server origin deploy asks to create and install the Slack app | +| `MDA_INGRESS_SECRET` | Yes when identity uses trusted loopback / `backend` auth | Trusted invoke from the Events path into the graph | +| `SLACK_CLIENT_ID` / `SLACK_CLIENT_SECRET` | Optional | Connect-with-Slack OIDC (copy from the app deploy created) | +| `MDA_PUBLIC_APP_URL` | Optional (required for Connect-with-Slack) | Browser UI origin shown in connect prompts and post-OAuth return | +| `MDA_PUBLIC_API_URL` | Optional (recommended on Host) | Public Agent Server URL used as Slack OAuth `redirect_uri` | +| `MDA_GUEST_SIGNING_KEY` | Optional (required for Connect-with-Slack / guest) | Signs guest tokens and OAuth state | - - Slack Bot Token Scopes listing app_mentions:read, channels:history, chat:write, and im:history - +`SLACK_BOT_TOKEN` and `SLACK_SIGNING_SECRET` are no longer on this list: deploy provisions them from the workspace's Slack connection and writes them onto the deployment itself. ## Deploy and smoke-test -1. Put Slack secrets in `.env` and ensure [identity](/langsmith/managed-deep-agents-identity) is declared. -2. Run `mda deploy` (or `mda dev` with a reachable Events URL). -3. Set the Slack Request URL to `https:///channels/slack/events` and verify it. -4. In Slack, `@mention` the bot in a channel where it is invited (or DM it if `direct_message` is enabled). +1. Connect Slack for your LangSmith workspace (**Settings → Integrations**) and ensure [identity](/langsmith/managed-deep-agents-identity) is declared. +2. Run `mda deploy`. On the first deploy the CLI warns that the Slack app was skipped—the deployment had no public URL yet. +3. Run `mda deploy` again. Deploy creates and installs the app, points its Events URL at the deployment, and subscribes it to your triggers' bot events. +4. In Slack, invite the bot to a channel and `@mention` it (or DM it if `direct_message` is enabled). 5. Confirm the bot shows a loading status (when supported) and posts a reply when `autoReply` is `true`. ## Optional: Connect-with-Slack -Connect-with-Slack maps a Slack user (`slack:T…:U…`) to a web/guest actor so the same person keeps one thread history across browser and Slack when `scoping.threads` is `"actor"`. +Connect-with-Slack maps a Slack user (`slack:T…:U…`) to a web/guest user so the same person keeps one thread history across browser and Slack when `scope.threads` is `"user"`. The OAuth routes mount automatically when a Slack channel is declared on a user-scoped deployment—you do not list the provider in `identity`. When OIDC is configured (`SLACK_CLIENT_ID`, `SLACK_CLIENT_SECRET`, `MDA_PUBLIC_APP_URL`, and a signing key such as `MDA_GUEST_SIGNING_KEY`): -- **Linked users** — Events remap to the web actor and the agent runs. +- **Linked users** — Events remap to the web user and the agent runs. - **Unlinked users** — The bot replies with a connect link; no agent run until they finish OAuth. -Shared-bot projects without OIDC keep Slack actors as-is (`slack:T…:U…`). +Shared-bot projects without OIDC keep Slack users as-is (`slack:T…:U…`). + +The client id and secret come from the Slack app deploy created: open the app in your Slack workspace settings and copy them into `.env` (or LangSmith workspace secrets). ### Slack OAuth redirect URLs @@ -230,27 +186,14 @@ Managed connect routes on the Agent Server: | `/identity/slack/status` | Link status for the signed-in web user | | `/identity/slack/link` | Link helpers used by the connect flow | - - Slack Redirect URLs showing https://…/identity/slack/callback saved for Connect-with-Slack OAuth - - - - Slack message from the MDA app telling an unlinked user to connect their account via a settings URL before using the agent - - ## Troubleshooting | Symptom | Likely cause | | --- | --- | -| Request URL verification fails | Wrong `SLACK_SIGNING_SECRET`, or Events URL path is not `/channels/slack/events` | -| Mentions work, plain thread replies do not | Missing `message.channels` / `message.groups` bot events or `channels:history` / `groups:history` scopes—add them, **reinstall**, reply inside the thread | -| Deploy fails citing Slack secrets | `channels/` present but `SLACK_SIGNING_SECRET` / `SLACK_BOT_TOKEN` missing from `.env` / workspace secrets | +| First deploy warns that the Slack app was skipped | Expected—the deployment had no public URL yet. Deploy again to connect the app. | +| Bot never responds in Slack | Slack not connected for the workspace (**Settings → Integrations**), `MDA_TRIGGER_SERVER_URL` unset, or the app was not connected yet (redeploy) | +| Mentions work, plain thread replies do not | `thread_reply` missing from `on`, the bot is not in the channel, or the reply was a new top-level message instead of a thread reply | +| Signature verification fails | Redeploy so deploy writes the current app's signing secret onto the deployment; remove any stale `SLACK_SIGNING_SECRET` from `.env` | | Connect OAuth redirects to `localhost` | Set `MDA_PUBLIC_API_URL` to the public Agent Server URL and redeploy | | Double replies on Host | Event dedupe is process-local; Slack retries can double-invoke on multi-replica Host | @@ -261,12 +204,12 @@ Managed connect routes on the Agent Server: See how channel discovery and Events ingress work. - Choose shared-bot vs linked validated_token for Slack callers. + Choose a shared bot vs linked validated-token auth for Slack callers. Route secrets and deploy the channel-enabled agent. - Look up `channels/` packaging and deploy preflight. + Look up `channels/` packaging and deploy behavior. diff --git a/src/langsmith/managed-deep-agents-cli.mdx b/src/langsmith/managed-deep-agents-cli.mdx index 3656ef775a..8a1dcb861b 100644 --- a/src/langsmith/managed-deep-agents-cli.mdx +++ b/src/langsmith/managed-deep-agents-cli.mdx @@ -51,11 +51,11 @@ LANGSMITH_API_KEY= OPENAI_API_KEY= ``` -To deploy with an organization-scoped key, set `LANGSMITH_TENANT_ID` or pass `--tenant-id` to `mda deploy`. +To deploy with an organization-scoped key, set `LANGCHAIN_WORKSPACE_ID` or pass `--workspace-id` to `mda deploy`. The retired `LANGSMITH_TENANT_ID` and `LANGCHAIN_TENANT_ID` names are no longer supported—deploy fails with a rename hint if either is set. The LangSmith API key authenticates the deploy. The agent's model provider also needs credentials at runtime. Set the provider key in `.env`, export it in your shell, or configure it as a LangSmith workspace secret. For example, `openai:gpt-5.5` requires `OPENAI_API_KEY`. -`mda deploy` forwards non-reserved `.env` entries, such as `OPENAI_API_KEY`, MCP tokens, and custom tool credentials, as hosted deployment secrets. Reserved platform variables, including `LANGSMITH_API_KEY`, `LANGGRAPH_HOST_API_KEY`, `LANGCHAIN_API_KEY`, and `LANGSMITH_TENANT_ID`, are used for CLI authentication and deploy routing but are not uploaded as user-managed deployment secrets. +`mda deploy` forwards non-reserved `.env` entries, such as `OPENAI_API_KEY`, MCP tokens, and custom tool credentials, as hosted deployment secrets. Reserved platform variables, including `LANGSMITH_API_KEY`, `LANGGRAPH_HOST_API_KEY`, `LANGCHAIN_API_KEY`, and `LANGCHAIN_WORKSPACE_ID`, are used for CLI authentication and deploy routing but are not uploaded as user-managed deployment secrets. ## Command overview @@ -76,9 +76,10 @@ Use `mda init` to create a new project directory: mda init my-agent ``` -| Argument | Use | +| Argument or flag | Use | | --- | --- | | `name` | Required project directory name. The command fails if the destination already exists. | +| `--scope ` | Scaffold an identity declaration for the given boundary: `user` (private threads and memory per person), `shared-conversations` (the conversation owns the thread), `organization` (many customer orgs on one deployment), `agent` (shared memory, no end user), or `none` (no durable memory or downstream credentials). Omit the flag for no identity file at all. | The command detects the language from the current directory: @@ -165,14 +166,14 @@ mda deploy . | `path` | Project directory. Defaults to the current directory. | | `--name NAME` | Deployment name. Defaults to the project directory name, normalized to lowercase letters, numbers, and hyphens. | | `--deployment-type dev\|prod` | Deployment type when creating a deployment. Defaults to `dev`. | -| `--tenant-id TENANT_ID` | Workspace or tenant ID. Overrides `LANGSMITH_TENANT_ID`. | +| `--workspace-id WORKSPACE_ID` | Workspace ID to deploy into. Overrides `LANGCHAIN_WORKSPACE_ID`. | | `--host-url URL` | Host backend API URL override. Defaults to US LangSmith Cloud. | | `--no-wait` | Trigger the remote build and exit without polling for deployment completion. | Deploy runs these steps: 1. Validate the project directory and load the agent entry file. -2. Resolve the LangSmith API key and optional tenant ID. +2. Resolve the LangSmith API key and optional workspace ID. 3. Collect non-reserved `.env` values as hosted deployment secrets. 4. Verify the model provider API key is available from `.env`, the shell environment, or LangSmith workspace secrets. 5. Sync deploy-owned context to Context Hub. @@ -206,9 +207,9 @@ For examples, see [Custom tools](/langsmith/managed-deep-agents-tools) and [Cust ### Identity -Optionally export a named `identity` declaration from a project-root `identity.ts` or `identity.py` created with `defineIdentity` / `define_identity` (or `.preset(...)`). +Optionally export a named `identity` declaration from a project-root `identity.ts` or `identity.py` created with `defineIdentity` / `define_identity`. -When present, `mda` generates the custom auth handler, injects it into the compiled app, and scopes threads, memory, and store access from the declaration. Projects without identity keep the previous compile output. For presets, ingress modes, guest tokens, and `runtime.identity`, see [Identity](/langsmith/managed-deep-agents-identity). +When present, `mda` generates the custom auth handler, injects it into the compiled app, and scopes threads, memory, and store access from the declaration. Projects without identity keep the previous compile output. For scopes, auth modes, guest tokens, and `runtime.identity`, see [Identity](/langsmith/managed-deep-agents-identity). ### Instructions @@ -226,19 +227,20 @@ Managed memory lives in the same Context Hub repo as the deployed instructions a ### Connectors -Declare connectors as modules directly under `connectors/`. Discovery is name-agnostic: each file is a connector module (package `__init__.py` files are ignored). +Declare connectors as modules directly under `connectors/`. Each file exports a named `connector` created from the `connectors` namespace (package `__init__.py` files are ignored). In TypeScript, an `export default` fails the build with the fix in the error message. -- **MCP:** `connectors/mcp.ts` or `connectors/mcp.py` must export a named `mcp` declaration. Supports remote `http` and `sse` servers; stdio is rejected. When present, `mda` injects `@langchain/mcp-adapters` or `langchain-mcp-adapters` and appends loaded MCP tools to authored tools. -- **GitHub:** `connectors/github.ts` or `connectors/github.py` declares repository checkouts, GitHub CLI installation, and credential injection for the managed sandbox. -- **LangSmith:** `connectors/langsmith.ts` or `connectors/langsmith.py` declares constrained LangSmith capabilities for untrusted callers. Requires [identity](/langsmith/managed-deep-agents-identity). The browser never receives `LANGSMITH_API_KEY`. +- **Integrations:** `connectors/gmail.ts`, `connectors/linear.ts`, and the other [tool server integrations](/langsmith/managed-deep-agents-connectors/integrations) load LangChain-authored tools through LangSmith's gateway for integrations the workspace connected in LangSmith. +- **MCP:** `connectors/mcp.ts` or `connectors/mcp.py` declares remote MCP servers with `connectors.mcp(...)`. Supports remote `http` and `sse` servers; stdio is rejected. When present, `mda` injects `@langchain/mcp-adapters` or `langchain-mcp-adapters` and appends loaded MCP tools to authored tools. +- **GitHub:** `connectors/github.ts` or `connectors/github.py` declares GitHub tool loading plus repository checkouts, GitHub CLI installation, and credential injection for the managed sandbox with `connectors.github(...)`. +- **LangSmith:** `connectors/langsmith.ts` or `connectors/langsmith.py` declares constrained LangSmith capabilities for untrusted callers with `connectors.langsmith(...)`. Requires [identity](/langsmith/managed-deep-agents-identity). The browser never receives `LANGSMITH_API_KEY`. For examples and defaults, see [Connectors](/langsmith/managed-deep-agents-connectors). ### Channels -Declare messaging channels as modules directly under `channels/`. Each file exports a named `channel` (for example `defineSlackChannel` / `defineGitHubChannel`). The file stem becomes the channel name and mounts `POST /channels/{name}/events` on the Agent Server. Channels require a root [identity](/langsmith/managed-deep-agents-identity) declaration. +Declare messaging channels as modules directly under `channels/`. Each file exports a named `channel` created from the `channels` namespace (for example `channels.slack(...)` / `channels.github(...)`). The file stem becomes the channel name and mounts `POST /channels/{name}/events` on the Agent Server. Channels require a root [identity](/langsmith/managed-deep-agents-identity) declaration. -- **Slack:** `channels/slack.ts` or `channels/slack.py`. Deploy preflights `SLACK_SIGNING_SECRET` and `SLACK_BOT_TOKEN` (from the channel manifest `requiredEnv`). +- **Slack:** `channels/slack.ts` or `channels/slack.py`. Deploy creates and installs the channel's Slack app through the workspace's Slack connection in LangSmith, subscribes it to the events the `on` triggers need, and writes the bot token and signing secret onto the deployment—there are no Slack secrets to author. One Slack app per deployment, so at most one Slack channel; the first deploy warns and the next one connects the app. - **GitHub:** `channels/github.ts` or `channels/github.py` with ordered `handlers` (`on`, `prompt`, optional `repositories` / `autoReply`). Deploy preflights `GITHUB_WEBHOOK_SECRET`, `GITHUB_APP_ID`, `GITHUB_APP_PRIVATE_KEY`, and `GITHUB_INSTALLATION_ID`. For handlers, triggers, and provider setup, see [Channels](/langsmith/managed-deep-agents-channels), [Slack](/langsmith/managed-deep-agents-channels/slack), and [GitHub](/langsmith/managed-deep-agents-channels/github). @@ -251,7 +253,7 @@ Deploy extracts schedule declarations from static literals, arrays, objects, and ### Sandbox -To configure a managed sandbox, export `sandbox` from `sandbox/index.ts` for TypeScript or `sandbox/__init__.py` for Python. Scope defaults to one sandbox per thread; `scope: "agent"` shares one across the agent process. `sandbox/setup.sh`, when present, runs once when a new managed sandbox is provisioned. +To configure a managed sandbox, export `sandbox` from `sandbox/index.ts` for TypeScript or `sandbox/__init__.py` for Python, created with `sandboxes.langsmith(...)`. Scope defaults to one sandbox per thread; `scope: "agent"` shares one across the agent process. `sandbox/setup.sh`, when present, runs once when a new managed sandbox is provisioned. For configuration examples and lifecycle behavior, see [Configure a sandbox](/langsmith/managed-deep-agents-deploy#configure-a-sandbox). diff --git a/src/langsmith/managed-deep-agents-connectors/github.mdx b/src/langsmith/managed-deep-agents-connectors/github.mdx index 1d9fa75621..726d59f018 100644 --- a/src/langsmith/managed-deep-agents-connectors/github.mdx +++ b/src/langsmith/managed-deep-agents-connectors/github.mdx @@ -1,13 +1,16 @@ --- title: Connect GitHub repositories to Managed Deep Agents sidebarTitle: GitHub -description: Clone GitHub repositories, install the GitHub CLI, and inject credentials into a Managed Deep Agents sandbox. +description: Load GitHub tools from the LangSmith tool server, clone repositories, install the GitHub CLI, and inject credentials into a Managed Deep Agents sandbox. --- import ManagedDeepAgentsPrivateBetaNote from '/snippets/langsmith/managed-deep-agents-private-beta-note.mdx'; import ManagedDeepAgentsTestAndDeploy from '/snippets/langsmith/managed-deep-agents-test-and-deploy.mdx'; -The GitHub connector prepares repositories, the `gh` CLI, and credentials inside a [managed sandbox](/langsmith/managed-deep-agents-deploy#configure-a-sandbox). Use it when the agent needs to inspect or change GitHub repositories. +The GitHub connector gives the agent two independent ways to work with GitHub: + +- **Tools** — GitHub is a [tool server integration](/langsmith/managed-deep-agents-connectors/integrations) like Gmail or Linear, so the connector can load LangChain-authored GitHub API tools through LangSmith's gateway. The provider token stays in LangSmith's vault. +- **Sandbox** — the connector also prepares repositories, the `gh` CLI, and credentials inside a [managed sandbox](/langsmith/managed-deep-agents-deploy#configure-a-sandbox), so the agent can inspect or change checkouts directly. The GitHub connector requires `managed-deepagents>=0.4.0`. @@ -21,14 +24,14 @@ This connector is separate from the [GitHub channel](/langsmith/managed-deep-age ## Add the connector -Create `connectors/github.py` or `connectors/github.ts`: +Create `connectors/github.py` or `connectors/github.ts` and export a named `connector`: ```python connectors/github.py -from managed_deepagents.connectors import github +from managed_deepagents import connectors -connector = github.connector( +connector = connectors.github( repositories=[ { "repo": "acme/api", @@ -42,9 +45,9 @@ connector = github.connector( ``` ```ts connectors/github.ts -import { github } from "managed-deepagents"; +import { connectors } from "managed-deepagents"; -export default github.connector({ +export const connector = connectors.github({ repositories: [ { repo: "acme/api", @@ -61,27 +64,64 @@ export default github.connector({ The connector clones each repository when the sandbox is created. On reuse, `on_reuse` / `onReuse` controls whether it keeps, resets, or fetches the checkout. The default is `fetch`. +## Tools and the `installCLI` rule + +The two halves meet in exactly one rule: **`installCLI` decides the default tool surface.** With `gh` in the sandbox (the default), the agent already reaches the GitHub API, so the integration's tool definitions stay off—adding them would be a second route to the same endpoints. Naming tools with `include_tools` / `includeTools`, or setting `installCLI: false`, turns them on. An explicit selection always wins, `exclude_tools` / `excludeTools` on its own included. + +That means a checkout-only project needs no tool config and no connected GitHub integration in the workspace—the gateway is never called: + + + +```python connectors/github.py +from managed_deepagents import connectors + +# Tools on: no gh in the sandbox, so the agent uses the integration's tools +connector = connectors.github( + install_cli=False, + include_tools=["github_create_pull_request"], +) +``` + +```ts connectors/github.ts +import { connectors } from "managed-deepagents"; + +// Tools on: no gh in the sandbox, so the agent uses the integration's tools +export const connector = connectors.github({ + installCLI: false, + includeTools: ["github_create_pull_request"], +}); +``` + + + +Tool names are provider-qualified (`github_create_pull_request`), not prefixed. For how the gateway resolves credentials, see [Tool server integrations](/langsmith/managed-deep-agents-connectors/integrations). + ## Configure options | Option (Python / TypeScript) | Default | Purpose | | --- | --- | --- | | `repositories` | `[]` | Repository checkouts and their sandbox paths. | -| `install_cli` / `installCLI` | `true` | Install the GitHub CLI in the sandbox. | +| `install_cli` / `installCLI` | `true` | Install the GitHub CLI in the sandbox. `true` also defaults the integration's tools off. | | `inject_credentials` / `injectCredentials` | `true` | Expose resolved GitHub credentials to `git` and `gh`. | +| `include_tools` / `includeTools` | all tools (when on) | Allowlist of integration tool names to load. | +| `exclude_tools` / `excludeTools` | _(none)_ | Denylist of integration tool names. | Repository paths must be relative and unique. Set `write` to `true` on a checkout that needs write credentials. -For private repositories, configure GitHub credentials through [identity](/langsmith/managed-deep-agents-identity#custom-downstream-credentials). The runtime injects the resolved token as `GH_TOKEN` and configures Git credentials without storing it in thread state. +For private repositories, configure GitHub credentials through [identity](/langsmith/managed-deep-agents-identity#downstream-credentials). The runtime injects the resolved token as `GH_TOKEN` and configures Git credentials without storing it in thread state. ## Test and deploy -The connector runs only when the project declares a managed sandbox. After startup, ask the agent to inspect the configured path or run `gh auth status`. +The sandbox half runs only when the project declares a managed sandbox. After startup, ask the agent to inspect the configured path or run `gh auth status`. ## Next steps + + See how LangSmith-hosted integration tools reach the agent. + Compare connector types. diff --git a/src/langsmith/managed-deep-agents-connectors/index.mdx b/src/langsmith/managed-deep-agents-connectors/index.mdx index 6c8dc1455d..c51227ec07 100644 --- a/src/langsmith/managed-deep-agents-connectors/index.mdx +++ b/src/langsmith/managed-deep-agents-connectors/index.mdx @@ -7,7 +7,7 @@ description: Add MCP tools, LangSmith capabilities, and GitHub sandbox access wi import ManagedDeepAgentsPrivateBetaNote from '/snippets/langsmith/managed-deep-agents-private-beta-note.mdx'; import ManagedDeepAgentsTestAndDeploy from '/snippets/langsmith/managed-deep-agents-test-and-deploy.mdx'; -Managed Deep Agents discovers connector modules under `connectors/`. Each file directly under that folder is a connector; you do not register connectors in the agent entry. +Managed Deep Agents discovers connector modules under `connectors/`. Each file directly under that folder is a connector; you do not register connectors in the agent entry. Every connector module exports a named `connector` created from the `connectors` namespace (for example `connectors.mcp(...)`); a TypeScript `export default` fails the build. @@ -17,9 +17,10 @@ Managed Deep Agents discovers connector modules under `connectors/`. Each file d | Connector | File | What it does | | --- | --- | --- | +| [Integrations](/langsmith/managed-deep-agents-connectors/integrations) | `connectors/gmail.{py\|ts}`, `connectors/linear.{py\|ts}`, … | Loads LangChain-authored tools for integrations the workspace connected in LangSmith (Gmail, Slack, Linear, Google Sheets, Tavily, …) through LangSmith's gateway. Provider tokens stay in LangSmith's vault. | | [MCP](/langsmith/managed-deep-agents-connectors/mcp) | `connectors/mcp.{py\|ts}` | Loads tools from remote MCP servers at runtime and appends them to authored tools. | | [LangSmith](/langsmith/managed-deep-agents-connectors/langsmith) | `connectors/langsmith.{py\|ts}` | Lets browsers and other untrusted callers invoke allowlisted LangSmith operations without receiving `LANGSMITH_API_KEY`. Requires [identity](/langsmith/managed-deep-agents-identity). | -| [GitHub](/langsmith/managed-deep-agents-connectors/github) | `connectors/github.{py\|ts}` | Clones repositories, installs `gh`, and injects credentials into the managed sandbox. | +| [GitHub](/langsmith/managed-deep-agents-connectors/github) | `connectors/github.{py\|ts}` | GitHub tools from the tool server, plus repository clones, `gh`, and credential injection in the managed sandbox. | For the full project layout, see the [CLI project file reference](/langsmith/managed-deep-agents-cli#project-file-reference). @@ -50,6 +51,9 @@ Connector misconfiguration usually surfaces during local startup or first tool l ## Next steps + + Add Gmail, Slack, Linear, and other workspace tools connected in LangSmith. + Load tools from remote MCP servers. diff --git a/src/langsmith/managed-deep-agents-connectors/integrations.mdx b/src/langsmith/managed-deep-agents-connectors/integrations.mdx new file mode 100644 index 0000000000..b56aa67c5a --- /dev/null +++ b/src/langsmith/managed-deep-agents-connectors/integrations.mdx @@ -0,0 +1,119 @@ +--- +title: Add LangSmith tool server integrations to Managed Deep Agents +sidebarTitle: Integrations +description: Give your agent Gmail, Slack, Linear, and other workspace tools connected once in LangSmith—without provider tokens ever reaching the deployment. +--- + +import ManagedDeepAgentsPrivateBetaNote from '/snippets/langsmith/managed-deep-agents-private-beta-note.mdx'; +import ManagedDeepAgentsTestAndDeploy from '/snippets/langsmith/managed-deep-agents-test-and-deploy.mdx'; + +The LangSmith tool server hosts LangChain-authored tools per provider—Gmail, Slack, Linear, Google Sheets, Tavily, and more—and publishes them over MCP, one endpoint per integration. Your workspace connects an integration once in LangSmith; declaring the matching connector is how a deployment gets those tools. The provider's OAuth tokens never leave LangSmith's vault: Managed Deep Agents calls LangSmith's gateway, which resolves the credential and forwards the call upstream, so the deployment never sees, stores, or refreshes a provider token. + + + + + +For other connector types, see [Connectors](/langsmith/managed-deep-agents-connectors). The [GitHub connector](/langsmith/managed-deep-agents-connectors/github) is one of these integrations too, documented separately because it also provisions the sandbox. + +## Add an integration + +Connect the integration for your workspace in LangSmith (**Settings → Integrations**), then declare the connector. Create `connectors/gmail.py` or `connectors/gmail.ts` and export a named `connector`: + + + +```python connectors/gmail.py +from managed_deepagents import connectors + +connector = connectors.gmail( + include_tools=["gmail_read_emails", "gmail_draft_email"], +) +``` + +```ts connectors/gmail.ts +import { connectors } from "managed-deepagents"; + +export const connector = connectors.gmail({ + includeTools: ["gmail_read_emails", "gmail_draft_email"], +}); +``` + + + +Every integration is reached the same way, as `connectors.(...)`: + +| Integration | Factory (TypeScript / Python) | +| --- | --- | +| Apollo | `connectors.apollo` | +| Ashby | `connectors.ashby` | +| Base (URL content & image extraction) | `connectors.base` | +| Exa | `connectors.exa` | +| Excel | `connectors.excel` | +| Gmail | `connectors.gmail` | +| Google BigQuery | `connectors.googleBigQuery` / `connectors.google_bigquery` | +| Google Calendar | `connectors.googleCalendar` / `connectors.google_calendar` | +| Google Docs | `connectors.googleDocs` / `connectors.google_docs` | +| Google Drive | `connectors.googleDrive` / `connectors.google_drive` | +| Google Meet | `connectors.googleMeet` / `connectors.google_meet` | +| Google Sheets | `connectors.googleSheets` / `connectors.google_sheets` | +| Google Slides | `connectors.googleSlides` / `connectors.google_slides` | +| Linear | `connectors.linear` | +| LinkedIn | `connectors.linkedin` | +| Outlook | `connectors.outlook` | +| PowerPoint | `connectors.powerpoint` | +| Pylon | `connectors.pylon` | +| Salesforce | `connectors.salesforce` | +| SharePoint | `connectors.sharepoint` | +| Slack | `connectors.slack` | +| Tavily | `connectors.tavily` | +| Microsoft Teams | `connectors.teams` | +| Word | `connectors.word` | +| X | `connectors.x` | + +The catalog is a superset of what any one workspace can use: an integration still has to be connected in LangSmith, which the runtime reports per call. + +## Select tools + +The config is only ever about which tools the agent gets: + +| Option | Default | Purpose | +| --- | --- | --- | +| `include_tools` / `includeTools` | all published tools | Allowlist of tool names to load. An empty list loads none and skips the gateway entirely. | +| `exclude_tools` / `excludeTools` | _(none)_ | Denylist of tool names. | +| `default_tool_timeout` / `defaultToolTimeout` | _(provider default)_ | Per-tool call timeout—seconds in Python, milliseconds in TypeScript. | + +Tool names are **not** prefixed: the tool server already publishes them provider-qualified (`gmail_send_email`, `linear_create_issue`), so `includeTools` matches what the provider publishes rather than a name Managed Deep Agents renamed. + +How the deployment reaches LangSmith is a fact about the deployment, not a choice the agent's source makes, so it comes from the environment—`LANGSMITH_API_KEY`, `LANGSMITH_ENDPOINT` / `LANGCHAIN_ENDPOINT`, `LANGCHAIN_WORKSPACE_ID`—the same way the [LangSmith connector](/langsmith/managed-deep-agents-connectors/langsmith) resolves those values. The schema is strict: passing `apiKey` or another connection option to the factory is an error, not a silently ignored key. + +## Whose credentials the tools use + +The gateway resolves credentials for whoever the call authenticates as: + +- **A deployment key** resolves the workspace's shared connection, so every thread reaches the same connected account. +- **A personal key** resolves that user's own connections, which is what makes `mda dev` work against your own Gmail while developing. +- **A key with no user behind it** has no subject at the gateway yet. Such a deployment borrows a LangSmith agent's shared connection by UUID through `MDA_TOOLSERVER_AGENT_ID`. This is temporary: it goes away once the gateway can resolve a deployment from its own API key. + +For per-user credentials—each caller reaching their own Gmail—use [identity credentials](/langsmith/managed-deep-agents-identity#downstream-credentials) with a resolver for `runtime.identity.user` instead of this connector. + +## Test and deploy + + + +A call against an integration the workspace has not connected fails with a gateway error naming the integration. Connect it in LangSmith and retry—no redeploy needed. + +## Next steps + + + + GitHub tools plus sandbox checkouts, `gh`, and credential injection. + + + Load tools from your own remote MCP servers. + + + Compare connector types. + + + Resolve per-user credentials for downstream calls. + + diff --git a/src/langsmith/managed-deep-agents-connectors/langsmith.mdx b/src/langsmith/managed-deep-agents-connectors/langsmith.mdx index 4db595c162..d92d22c76e 100644 --- a/src/langsmith/managed-deep-agents-connectors/langsmith.mdx +++ b/src/langsmith/managed-deep-agents-connectors/langsmith.mdx @@ -19,26 +19,30 @@ For other connector types, see [Connectors](/langsmith/managed-deep-agents-conne ## Add a LangSmith connector -Add `connectors/langsmith.py` or `connectors/langsmith.ts` next to your agent entry file. Start with presets for the common browser surfaces, or compose [custom grants](#custom-capability-grants) when you need different scopes or constraints. +Add `connectors/langsmith.py` or `connectors/langsmith.ts` next to your agent entry file and export a named `connector`. Start with presets for the common browser surfaces, or compose [custom grants](#custom-capability-grants) when you need different scopes or constraints. The presets and builders live on the same `connectors.langsmith` namespace: ```python connectors/langsmith.py -from managed_deepagents.connectors import langsmith +from managed_deepagents import connectors -connector = langsmith.connector( - langsmith.chat_feedback(dataset="public-feedback"), - langsmith.trace_viewer(), +connector = connectors.langsmith( + capabilities=[ + connectors.langsmith.chat_feedback(dataset="public-feedback"), + connectors.langsmith.trace_viewer(), + ], ) ``` ```ts connectors/langsmith.ts -import { langsmith } from "managed-deepagents"; - -export default langsmith.connector( - langsmith.chatFeedback({ dataset: "public-feedback" }), - langsmith.traceViewer(), -); +import { connectors } from "managed-deepagents"; + +export const connector = connectors.langsmith({ + capabilities: [ + connectors.langsmith.chatFeedback({ dataset: "public-feedback" }), + connectors.langsmith.traceViewer(), + ], +}); ``` @@ -58,12 +62,12 @@ URL-encode the capability id when it contains `:` (for example `langsmith%3Achat ### Authenticate -The route uses the same [identity ingress](/langsmith/managed-deep-agents-identity#ingress-identify-the-caller) as agent runs. Include identity headers on every request: +The route uses the same [identity auth](/langsmith/managed-deep-agents-identity#auth-identify-the-caller) as agent runs. Include identity headers on every request: -| Ingress | Headers | +| Auth mode | Headers | | --- | --- | | Validated token (browser-direct) | `Authorization: Bearer ` | -| Trusted backend | `X-MDA-Ingress-Secret`, `X-MDA-Actor-Id`, and `X-MDA-Tenant-Id` when multi-tenant | +| Backend | `X-MDA-Ingress-Secret`, `X-MDA-User-Id`, and `X-MDA-Organization-Id` when organizations are required | Unauthenticated calls return `401`. Ownership failures return `403`. @@ -169,16 +173,16 @@ Presets expand to the stable capability ids in the table above. ### Chat feedback -`chatFeedback` / `chat_feedback` exposes create, update, and delete for one bounded feedback key per actor on a run, and can create an example in a fixed dataset from the same conversation. +`chatFeedback` / `chat_feedback` exposes create, update, and delete for one bounded feedback key per user on a run, and can create an example in a fixed dataset from the same conversation. ```python -langsmith.chat_feedback(dataset="public-feedback") +connectors.langsmith.chat_feedback(dataset="public-feedback") ``` ```ts -langsmith.chatFeedback({ dataset: "public-feedback" }) +connectors.langsmith.chatFeedback({ dataset: "public-feedback" }) ``` @@ -190,53 +194,59 @@ langsmith.chatFeedback({ dataset: "public-feedback" }) - **`langsmith:chat-feedback`** — run-scoped feedback for browsers. Key `user_score`, scores `positive` / `negative`, comments up to 2000 characters, `onePerActor`. Response fields: `id`, `run_id`, `key`, `score`, `created_at`. - **`langsmith:chat-feedback-examples`** — thread-scoped example create. Allowed fields: `messages`, `answer`, `feedback`, `source`. Response fields: `id`, `dataset_id`, `created_at`. +Presets return a list of grants, so they nest directly inside `capabilities` (the list is flattened). + ```python - langsmith.connector( - langsmith.feedback( - id="langsmith:chat-feedback", - expose_to=["browser"], - actions=["create", "update", "delete"], - scope="run", - keys=["user_score"], - scores=["positive", "negative"], - max_comment_chars=2000, - one_per_actor=True, - ), - langsmith.examples( - id="langsmith:chat-feedback-examples", - expose_to=["browser"], - actions=["create"], - scope="thread", - dataset="public-feedback", - allowed_fields=["messages", "answer", "feedback", "source"], - ), + connectors.langsmith( + capabilities=[ + connectors.langsmith.feedback( + id="langsmith:chat-feedback", + expose_to=["browser"], + actions=["create", "update", "delete"], + scope="run", + keys=["user_score"], + scores=["positive", "negative"], + max_comment_chars=2000, + one_per_actor=True, + ), + connectors.langsmith.examples( + id="langsmith:chat-feedback-examples", + expose_to=["browser"], + actions=["create"], + scope="thread", + dataset="public-feedback", + allowed_fields=["messages", "answer", "feedback", "source"], + ), + ], ) ``` ```ts - langsmith.connector( - langsmith.feedback({ - id: "langsmith:chat-feedback", - exposeTo: ["browser"], - actions: ["create", "update", "delete"], - scope: "run", - keys: ["user_score"], - scores: ["positive", "negative"], - maxCommentChars: 2000, - onePerActor: true, - }), - langsmith.examples({ - id: "langsmith:chat-feedback-examples", - exposeTo: ["browser"], - actions: ["create"], - scope: "thread", - dataset: "public-feedback", - allowedFields: ["messages", "answer", "feedback", "source"], - }), - ); + connectors.langsmith({ + capabilities: [ + connectors.langsmith.feedback({ + id: "langsmith:chat-feedback", + exposeTo: ["browser"], + actions: ["create", "update", "delete"], + scope: "run", + keys: ["user_score"], + scores: ["positive", "negative"], + maxCommentChars: 2000, + onePerActor: true, + }), + connectors.langsmith.examples({ + id: "langsmith:chat-feedback-examples", + exposeTo: ["browser"], + actions: ["create"], + scope: "thread", + dataset: "public-feedback", + allowedFields: ["messages", "answer", "feedback", "source"], + }), + ], + }); ``` @@ -249,11 +259,11 @@ langsmith.chatFeedback({ dataset: "public-feedback" }) ```python -langsmith.trace_viewer() +connectors.langsmith.trace_viewer() ``` ```ts -langsmith.traceViewer() +connectors.langsmith.traceViewer() ``` @@ -264,25 +274,29 @@ Expands to **`langsmith:trace-viewer`**: thread-scoped `runs` with actions `read ```python - langsmith.connector( - langsmith.runs( - id="langsmith:trace-viewer", - expose_to=["browser"], - actions=["read", "share"], - scope="thread", - ) + connectors.langsmith( + capabilities=[ + connectors.langsmith.runs( + id="langsmith:trace-viewer", + expose_to=["browser"], + actions=["read", "share"], + scope="thread", + ), + ], ) ``` ```ts - langsmith.connector( - langsmith.runs({ - id: "langsmith:trace-viewer", - exposeTo: ["browser"], - actions: ["read", "share"], - scope: "thread", - }), - ); + connectors.langsmith({ + capabilities: [ + connectors.langsmith.runs({ + id: "langsmith:trace-viewer", + exposeTo: ["browser"], + actions: ["read", "share"], + scope: "thread", + }), + ], + }); ``` @@ -290,14 +304,14 @@ Expands to **`langsmith:trace-viewer`**: thread-scoped `runs` with actions `read ## Custom capability grants -When a preset is too narrow, compose builders yourself: `runs`, `feedback`, `examples`, `threads`, `prompts`, and `annotationQueues` / `annotation_queues`. +When a preset is too narrow, compose builders yourself from the same namespace: `connectors.langsmith.runs`, `.feedback`, `.examples`, `.threads`, `.prompts`, and `.annotationQueues` / `.annotation_queues`. Each grant needs: - A stable `id` — becomes `{capability_id}` in the HTTP path -- `exposeTo` / `expose_to` — who may call it (`browser`, `trusted_backend`, `channel`, `schedule`) +- `exposeTo` / `expose_to` — who may call it (`browser`, `backend`, `channel`, `schedule`) - `actions` — allowed values for the body's `action` field -- `scope` — ownership boundary (`agent`, `tenant`, `actor`, `thread`, `run`) +- `scope` — ownership boundary (`agent`, `organization`, `user`, `thread`, `run`) Optional constraints and response shaping (`include`, `redact`, `allowSensitive` / `allow_sensitive`) keep browser responses small and fail closed on sensitive fields. diff --git a/src/langsmith/managed-deep-agents-connectors/mcp.mdx b/src/langsmith/managed-deep-agents-connectors/mcp.mdx index c538d3e7ad..0962633f97 100644 --- a/src/langsmith/managed-deep-agents-connectors/mcp.mdx +++ b/src/langsmith/managed-deep-agents-connectors/mcp.mdx @@ -7,7 +7,7 @@ description: Declare remote MCP servers with Managed Deep Agents connectors. import ManagedDeepAgentsPrivateBetaNote from '/snippets/langsmith/managed-deep-agents-private-beta-note.mdx'; import ManagedDeepAgentsTestAndDeploy from '/snippets/langsmith/managed-deep-agents-test-and-deploy.mdx'; -Managed Deep Agents use MCP connectors to load tools from remote MCP servers. Declare the servers in `connectors/mcp.ts` or `connectors/mcp.py`, export a named `mcp` declaration, and Managed Deep Agents loads those tools into the agent at runtime. +Managed Deep Agents use MCP connectors to load tools from remote MCP servers. Declare the servers in `connectors/mcp.ts` or `connectors/mcp.py`, export a named `connector` declaration, and Managed Deep Agents loads those tools into the agent at runtime. @@ -19,14 +19,14 @@ For other connector types, see [Connectors](/langsmith/managed-deep-agents-conne Add `connectors/mcp.py` or `connectors/mcp.ts` next to your agent entry file. -The connector module must export a named `mcp` declaration. +Like every connector module, it must export a named `connector` declaration. A TypeScript `export default` fails the build with the fix in the error message. ```python connectors/mcp.py -from managed_deepagents.connectors import define_mcp_servers +from managed_deepagents import connectors -mcp = define_mcp_servers( +connector = connectors.mcp( mcp_servers={ "langchainDocs": { "transport": "http", @@ -37,9 +37,9 @@ mcp = define_mcp_servers( ``` ```ts connectors/mcp.ts -import { defineMcpServers } from "managed-deepagents"; +import { connectors } from "managed-deepagents"; -export const mcp = defineMcpServers({ +export const connector = connectors.mcp({ mcpServers: { langchainDocs: { transport: "http", @@ -77,9 +77,9 @@ The connector module is normal project code, so read secrets as environment vari ```python connectors/mcp.py import os -from managed_deepagents.connectors import define_mcp_servers +from managed_deepagents import connectors -mcp = define_mcp_servers( +connector = connectors.mcp( mcp_servers={ "github": { "transport": "http", @@ -93,9 +93,9 @@ mcp = define_mcp_servers( ``` ```ts connectors/mcp.ts -import { defineMcpServers } from "managed-deepagents"; +import { connectors } from "managed-deepagents"; -export const mcp = defineMcpServers({ +export const connector = connectors.mcp({ mcpServers: { github: { transport: "http", diff --git a/src/langsmith/managed-deep-agents-deploy.mdx b/src/langsmith/managed-deep-agents-deploy.mdx index 289be45732..d5be4b88e8 100644 --- a/src/langsmith/managed-deep-agents-deploy.mdx +++ b/src/langsmith/managed-deep-agents-deploy.mdx @@ -77,11 +77,9 @@ Use a sandbox when the agent needs isolated code execution or filesystem work. E ```python sandbox/__init__.py -from managed_deepagents import define_sandbox -from deepagents.backends import LangSmithSandbox +from managed_deepagents import sandboxes -sandbox = define_sandbox( - LangSmithSandbox, +sandbox = sandboxes.langsmith( scope="thread", idle_ttl_seconds=600, default_timeout=600, @@ -89,10 +87,9 @@ sandbox = define_sandbox( ``` ```ts sandbox/index.ts -import { defineSandbox } from "managed-deepagents"; -import { LangSmithSandbox } from "deepagents"; +import { sandboxes } from "managed-deepagents"; -export const sandbox = defineSandbox(LangSmithSandbox, { +export const sandbox = sandboxes.langsmith({ scope: "thread", idleTtlSeconds: 600, defaultTimeout: 600, @@ -183,7 +180,7 @@ DATABASE_URL= `LANGSMITH_API_KEY`, `LANGGRAPH_HOST_API_KEY`, `LANGCHAIN_API_KEY`, and other platform variables are reserved. They can authenticate the deploy, but they are not uploaded as user-managed deployment secrets. -Non-reserved `.env` entries, such as model provider keys, MCP tokens, channel secrets, and custom tool credentials, are forwarded as hosted deployment secrets when `mda deploy` creates or updates the deployment. If the configured model requires a provider key, deploy fails before upload unless that key is available from `.env`, the shell environment, or LangSmith workspace secrets. When the provider key is only in the shell environment, `mda deploy` forwards it as a secret for that deploy. When the project declares `channels/`, deploy also preflights each channel manifest’s `requiredEnv` (for example Slack or GitHub App secrets)—see [Channels](/langsmith/managed-deep-agents-channels). +Non-reserved `.env` entries, such as model provider keys, MCP tokens, channel secrets, and custom tool credentials, are forwarded as hosted deployment secrets when `mda deploy` creates or updates the deployment. If the configured model requires a provider key, deploy fails before upload unless that key is available from `.env`, the shell environment, or LangSmith workspace secrets. When the provider key is only in the shell environment, `mda deploy` forwards it as a secret for that deploy. When the project declares `channels/`, deploy also arranges what each channel needs: a Slack channel's app is created and installed through the workspace's Slack connection (its token and signing secret are written onto the deployment, not read from `.env`), and a GitHub channel preflights the App secrets in its manifest’s `requiredEnv`—see [Channels](/langsmith/managed-deep-agents-channels). Reserved platform variables, empty values, `.env`, and `.env.*` files are not copied into the compiled build archive. diff --git a/src/langsmith/managed-deep-agents-evals.mdx b/src/langsmith/managed-deep-agents-evals.mdx index e087978240..4afafe0aae 100644 --- a/src/langsmith/managed-deep-agents-evals.mdx +++ b/src/langsmith/managed-deep-agents-evals.mdx @@ -109,18 +109,19 @@ For multi-metric rewards, verifier env vars in `task.toml`, and LLM-as-a-judge p ### Identity-aware projects -If the project exports [identity](/langsmith/managed-deep-agents-identity) (`identity.ts` or `identity.py`), every eval task must include `identity.json`. Scaffolding adds a default fixture automatically. Customize the fixture when your agent or tests depend on a specific actor, tenant, or claims. +If the project exports [identity](/langsmith/managed-deep-agents-identity) (`identity.ts` or `identity.py`), every eval task must include `identity.json`. Scaffolding adds a default fixture automatically. Customize the fixture when your agent or tests depend on a specific user, organization, groups, or claims. ```json identity.json { - "actor": { - "type": "user", + "user": { + "kind": "person", "id": "eval_user_1", "email": "eval@example.com" }, - "tenant": { + "organization": { "id": "acme" }, + "groups": ["billing"], "source": { "provider": "cli" }, diff --git a/src/langsmith/managed-deep-agents-how-it-works.mdx b/src/langsmith/managed-deep-agents-how-it-works.mdx index 16233b5a7a..613ab3a899 100644 --- a/src/langsmith/managed-deep-agents-how-it-works.mdx +++ b/src/langsmith/managed-deep-agents-how-it-works.mdx @@ -56,7 +56,7 @@ Edit instructions and skills in your project and redeploy. Memory is runtime-own The managed runtime owns the checkpointer and store, so each thread's state persists across runs without any setup. Durable memory persists in [Context Hub](#context-hub) and is available to the agent across threads. -When you declare [identity](/langsmith/managed-deep-agents-identity), Managed Deep Agents scopes threads and remounts the matching memory slice for the authenticated actor or tenant so callers cannot open each other's conversations or memory. Without identity, the deployment uses shared agent memory. +When you declare [identity](/langsmith/managed-deep-agents-identity), Managed Deep Agents scopes threads and remounts the matching memory slice for the authenticated user or organization so callers cannot open each other's conversations or memory. Without identity, the deployment uses shared agent memory. Scheduled runs choose their thread behavior explicitly. An ephemeral thread is cleaned up after the run, while a persistent thread reuses a stable thread ID so state accumulates. For the thread modes and when to use each, see [Schedules](/langsmith/managed-deep-agents-schedules). diff --git a/src/langsmith/managed-deep-agents-identity.mdx b/src/langsmith/managed-deep-agents-identity.mdx index a1913a906a..8485a9b189 100644 --- a/src/langsmith/managed-deep-agents-identity.mdx +++ b/src/langsmith/managed-deep-agents-identity.mdx @@ -31,12 +31,12 @@ Without identity, a Managed Deep Agent has one shared boundary for the whole dep Deep Agents without identity make this a real problem: they keep durable memory, resume long-running threads, and call tools on the user's behalf. Identity turns "who is calling?" into enforced isolation instead of hoping the prompt or the UI keeps people apart. -A key benefit of identity is that downstream tool calls can act **as the signed-in user** rather than as a shared bot account. For example, with `credentials: "actor"`, the agent calls GitHub as Alice, not as a single bot token shared across all users. +A key benefit of identity is that downstream tool calls can act **as the signed-in user** rather than as a shared bot account. For example, with user-scoped credentials, the agent calls GitHub as Alice, not as a single bot token shared across all users. For deployments with compliance requirements such as SOC 2, GDPR, or HIPAA, identity scoping provides the data segregation boundaries that auditors expect: each caller's threads and memory are isolated, and `runtime.identity` gives you an audit trail of who triggered each run. -Adding identity to a project that previously had none does not delete existing threads or memory. Threads created before identity was enabled remain accessible at the agent scope. New threads are scoped by actor (or tenant) according to your declaration. To migrate old data, export it and re-create threads under the new scoping rules. +Adding identity to a project that previously had none does not delete existing threads or memory. Threads created before identity was enabled remain accessible at the agent scope. New threads are scoped by user (or organization) according to your declaration. To migrate old data, export it and re-create threads under the new scoping rules. ## Understand three core concepts @@ -45,17 +45,17 @@ Learn these three concepts before you write any identity config: | Idea | Plain meaning | Example | | --- | --- | --- | -| **Actor** | The person or service this run is for | `user_123`, a GitHub login, a guest id | -| **Tenant** (optional) | The customer or org boundary when one deployment serves many orgs | `acme`, a Slack workspace | -| **Ingress** | How the runtime learns who is calling for this request | Your backend sends identity headers, or the browser sends a verified login token | +| **User** | The person or service this run is for | `user_123`, a GitHub login, a guest id | +| **Organization** (optional) | The customer or org boundary when one deployment serves many orgs | `acme`, a Slack workspace | +| **Auth** | How the runtime learns who is calling for this request | Your backend sends identity headers, or the browser sends a verified login token | A few important clarifications: -- **Actor** is not the agent. It is the caller the run represents. -- **Tenant** is not a LangSmith workspace. Single-tenant agents have no tenant. -- **Fail closed** means the runtime rejects any request that is missing a required actor or tenant. It never falls back to shared memory or threads. +- **User** is not the agent. It is the caller the run represents, and it can be a person or a service (`user.kind`). +- **Organization** is not a LangSmith workspace. Single-organization agents have no organization. Users can also carry a read-only `groups` list (every group the caller's token asserts) for authorization checks inside tools; isolation still keys on the single-valued organization. +- **Fail closed** means the runtime rejects any request that is missing a required user or organization. It never falls back to shared memory or threads. -From actor (and optional tenant), Managed Deep Agents derives three outcomes: +From the user (and optional organization), Managed Deep Agents derives three outcomes: - **Threads**: who can open or resume a conversation - **Memory**: which durable [Context Hub](/langsmith/managed-deep-agents-memory) slice the run can see @@ -63,8 +63,8 @@ From actor (and optional tenant), Managed Deep Agents derives three outcomes: ```mermaid flowchart LR - Caller["Caller"] --> Ingress["Ingress authenticates request"] - Ingress --> Resolve["Resolve actor and tenant"] + Caller["Caller"] --> Ingress["Auth authenticates request"] + Ingress --> Resolve["Resolve user and organization"] Resolve --> Scope["Scope threads and memory"] Resolve --> Reject["Reject: 403"] Scope --> Run["Run agent with runtime.identity"] @@ -79,63 +79,61 @@ flowchart LR class Reject alert; ``` -## Choose a preset +## Choose a scope -Presets encode the common product shapes so you do not invent scoping rules on day one. Start here, then override only what differs. +`scope` is the isolation boundary for the deployment. One value covers the common product shapes; per-axis overrides handle the exceptions. -The preset table uses these scope values: +The scope values: | Value | Meaning | | --- | --- | -| `actor` | Private to the signed-in person (or service actor) | -| `tenant` | Shared inside one customer org, isolated from other orgs | -| `channel` | Shared by everyone in the same channel (for example Slack) | +| `user` | Private to the signed-in person (or service user) | +| `organization` | Shared inside one customer org, isolated from other orgs | +| `conversation` | Shared by everyone in the same channel conversation (threads axis only) | | `agent` | Shared by the whole deployment | | _(unset)_ / `none` | Not scoped on this axis | **Credentials** is often the first thing teams consider: -- **`actor`**: downstream calls can act as the signed-in user (for example call GitHub as Alice). +- **`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. -Managed Deep Agents ships with five product shapes out of the box, covering the most common deployment patterns. Choose a preset based on your product shape: +Choose the declaration that matches your product shape: -| Preset | Use it when… | Threads | Memory | Credentials | +| Product shape | Declaration | Threads | Memory | Credentials | | --- | --- | --- | --- | --- | -| `private-assistant` | Each person gets a private 1:1 assistant with their own history and memory | `actor` | `actor` | `actor` | -| `multi-tenant-saas` | One deployment serves many customer orgs; users share org data but not across orgs | `actor` | `tenant` | `agent` | -| `shared-bot` | A Slack/Discord-style bot where everyone in the channel shares the thread | `channel` | `actor` | `agent` | -| `internal-tool` | An internal company agent: one org, private per-user threads | `actor` | `actor` | `agent` | -| `service` | Cron/webhook-only agents with no human caller and shared memory | _(unset)_ | `agent` | `agent` | +| 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` | -All presets default to `trusted_backend` ingress and `tenancy: "single"`, except `multi-tenant-saas`, which sets `tenancy: "multi"`. +All declarations default to `backend` auth (your backend asserts the caller) and optional organizations, except `scope: "organization"`, which requires an organization on every request. **How to choose quickly:** -- One human per conversation who must not see anyone else's data → `private-assistant` -- SaaS with customer orgs → `multi-tenant-saas` -- Shared channel bot → `shared-bot` -- Internal company tool → `internal-tool` -- Timer or webhook with no user → `service` +- One human per conversation who must not see anyone else's data → `defineIdentity()` (the default) +- SaaS with customer orgs → `defineIdentity({ scope: "organization" })` +- Shared channel bot → `defineIdentity({ scope: { threads: "conversation" } })` +- Timer or webhook with no user → `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 start from a one-line preset: +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: ```python identity.py from managed_deepagents import define_identity -identity = define_identity.preset("private-assistant") +identity = define_identity() ``` ```ts identity.ts import { defineIdentity } from "managed-deepagents"; -export const identity = defineIdentity.preset("private-assistant"); +export const identity = defineIdentity(); ``` @@ -148,12 +146,11 @@ That expands to this full contract: from managed_deepagents import define_identity identity = define_identity( - ingress={"http": "trusted_backend"}, - tenancy="single", - scoping={ - "threads": "actor", - "memory": "actor", - "credentials": "actor", + auth="backend", + scope={ + "threads": "user", + "memory": "user", + "credentials": "user", }, ) ``` @@ -162,61 +159,61 @@ identity = define_identity( import { defineIdentity } from "managed-deepagents"; export const identity = defineIdentity({ - ingress: { http: "trusted_backend" }, - tenancy: "single", - scoping: { - threads: "actor", - memory: "actor", - credentials: "actor", + auth: "backend", + scope: { + threads: "user", + memory: "user", + credentials: "user", }, }); ``` -Use the full form when you want every field visible, or when you are assembling a config that does not match a preset. You can also start from a preset and override only the fields that differ. The same `define_identity` / `defineIdentity` object serves as both a factory (full form) and a preset selector (`.preset()` method). +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). When identity is present, `mda` generates the custom auth handler, injects it into the compiled LangGraph app, and only then enables reserved identity headers and token verification. -## Ingress: identify the caller +## Auth: identify the caller -Ingress is the mechanism the runtime uses to identify the actor (and tenant) for each request. Choose one HTTP mode: `trusted_backend` or `validated_token`. +`auth` is the mechanism the runtime uses to identify the user (and organization) for each request. Choose one HTTP mode: `"backend"` or a validated-token provider list. -### Trusted backend (recommended default) +### Backend (recommended default) Your own API authenticates the user (session, OAuth, or similar), then proxies LangGraph requests with a shared ingress secret and reserved identity headers. The browser never sends the secret or raw identity-provider (IdP) tokens to Managed Deep Agents. -This is the default ingress for all presets, and the recommended choice when you already have a backend in front of the agent. For the broader LangGraph auth model, see [Add auth to your server](/langsmith/add-auth-server). +This is the default, and the recommended choice when you already have a backend in front of the agent. For the broader LangGraph auth model, see [Add auth to your server](/langsmith/add-auth-server). Required headers (case-insensitive): | Header | Required | Purpose | | --- | --- | --- | | `X-MDA-Ingress-Secret` | Yes | Shared secret from `MDA_INGRESS_SECRET` | -| `X-MDA-Actor-Id` | Yes | Actor id for this run | -| `X-MDA-Tenant-Id` | When `tenancy: "multi"` | Tenant id for this run | +| `X-MDA-User-Id` | Yes | User id for this run | +| `X-MDA-Organization-Id` | When organizations are required | Organization id for this run | +| `X-MDA-Groups` | No | Comma- or space-delimited group ids, exposed as `identity.groups` | -Use a preset that defaults to trusted-backend ingress: +The default declaration already uses backend auth, so there is nothing to set: ```python identity.py from managed_deepagents import define_identity -identity = define_identity.preset("internal-tool") +identity = define_identity() ``` ```ts identity.ts import { defineIdentity } from "managed-deepagents"; -export const identity = defineIdentity.preset("internal-tool"); +export const identity = defineIdentity(); ``` -Put `MDA_INGRESS_SECRET` in `.env` for `mda dev` and as a hosted deployment secret for `mda deploy`. In production, your backend authenticates the user, then attaches the identity headers (`X-MDA-Ingress-Secret`, `X-MDA-Actor-Id`, and `X-MDA-Tenant-Id` when applicable) when proxying agent traffic. +Put `MDA_INGRESS_SECRET` in `.env` for `mda dev` and as a hosted deployment secret for `mda deploy`. In production, your backend authenticates the user, then attaches the identity headers (`X-MDA-Ingress-Secret`, `X-MDA-User-Id`, and `X-MDA-Organization-Id` when applicable) when proxying agent traffic. Example shape for a backend proxy (pseudocode): @@ -227,8 +224,8 @@ await fetch(`${deploymentUrl}/threads/${threadId}/runs`, { headers: { "Content-Type": "application/json", "X-MDA-Ingress-Secret": process.env.MDA_INGRESS_SECRET!, - "X-MDA-Actor-Id": authenticatedUser.id, - // "X-MDA-Tenant-Id": org.id, // only when tenancy is "multi" + "X-MDA-User-Id": authenticatedUser.id, + // "X-MDA-Organization-Id": org.id, // only when organizations are required }, body: JSON.stringify(runBody), }); @@ -240,7 +237,7 @@ Never commit ingress secrets or IdP credentials. Only send `MDA_INGRESS_SECRET` ### Validated token (browser-direct) -Use this when the browser talks to the deployment directly and you do not want a proxy that asserts actor headers. +Use this when the browser talks to the deployment directly and you do not want a proxy that asserts user headers. The client sends `Authorization: Bearer `. Managed Deep Agents verifies the token server-side and maps claims (fields inside the token, such as user id) into `runtime.identity`. @@ -251,48 +248,35 @@ Verification can use: - **Opaque introspection**: call the IdP to ask whether a non-JWT token is still valid - **Guest tokens**: short-lived tokens signed by Managed Deep Agents for anonymous visitors -Override a preset to enable validated-token ingress. The following example combines Supabase sign-in with optional guest access: +Pass one provider or a list to `auth` to enable validated-token ingress. The following example combines Supabase sign-in with optional guest access: ```python identity.py -from managed_deepagents import define_identity, providers +from managed_deepagents import auth, define_identity -identity = define_identity.preset( - "internal-tool", - { - "ingress": { - "http": { - "mode": "validated_token", - "providers": [ - providers.supabase(project_ref="your-project-ref"), - providers.guest(ttl="24h", actor_prefix="guest:"), - ], - } - } - }, +identity = define_identity( + auth=[ + auth.supabase(project_ref="your-project-ref"), + auth.guest(ttl="24h", user_prefix="guest:"), + ], ) ``` ```ts identity.ts -import { defineIdentity, providers } from "managed-deepagents"; - -export const identity = defineIdentity.preset("internal-tool", { - ingress: { - http: { - mode: "validated_token", - providers: [ - providers.supabase({ projectRef: "your-project-ref" }), - providers.guest({ ttl: "24h", actorPrefix: "guest:" }), - ], - }, - }, +import { auth, defineIdentity } from "managed-deepagents"; + +export const identity = defineIdentity({ + auth: [ + auth.supabase({ projectRef: "your-project-ref" }), + auth.guest({ ttl: "24h", userPrefix: "guest:" }), + ], }); ``` -In `validated_token` mode, your frontend signs the user in with the same IdP you configured, reads an access token (or ID token where applicable), and passes it to the LangGraph client as `Authorization: Bearer `. Do not send refresh tokens or client secrets to the deployment. +In validated-token mode, your frontend signs the user in with the same IdP you configured, reads an access token (or ID token where applicable), and passes it to the LangGraph client as `Authorization: Bearer `. Do not send refresh tokens or client secrets to the deployment. When you configure more than one provider, give each entry a unique `id`. The runtime routes JWT providers by token `iss` (issuer) and returns 401 when the issuer does not match any configured provider. @@ -302,7 +286,7 @@ For provider-specific options and client examples, see [Provider setup guides](# | Secret | How Managed Deep Agents uses it | | --- | --- | -| `MDA_INGRESS_SECRET` | Shared secret your backend sends in `X-MDA-Ingress-Secret`. The runtime checks it before trusting `X-MDA-Actor-Id` and `X-MDA-Tenant-Id`. | +| `MDA_INGRESS_SECRET` | Shared secret your backend sends in `X-MDA-Ingress-Secret`. The runtime checks it before trusting `X-MDA-User-Id` and `X-MDA-Organization-Id`. | | `MDA_GUEST_SIGNING_KEY` | Key used to sign guest tokens at `POST /identity/guest` and to verify them on later requests. | Put local values in `.env`. `mda deploy` forwards non-reserved `.env` values as hosted deployment secrets. Provider-specific secrets (for example Supabase introspection) are listed in [Provider setup guides](#provider-setup-guides). @@ -319,13 +303,14 @@ The identity object looks like this: ```ts runtime.identity = { - actor: { type: "user" | "service", id: string, email?: string }, - tenant?: { id: string }, + user: { kind: "person" | "service", id: string, email?: string }, + organization?: { id: string }, + groups?: readonly string[], source: { provider: "http" | "slack" | "schedule" | "cli" | "studio", threadId?: string, }, - claims?: Record, // populated for validated_token ingress + claims?: Record, // populated for validated-token auth }; ``` @@ -340,11 +325,11 @@ from managed_deepagents import ManagedDeepAgentRuntime @tool def whoami(runtime: ManagedDeepAgentRuntime) -> str: - """Return the authenticated actor id for this run.""" + """Return the authenticated user id for this run.""" identity = runtime.identity if not identity: return "No authenticated caller on this run." - return f"Signed in as {identity['actor']['id']}" + return f"Signed in as {identity['user']['id']}" ``` ```ts tools/whoami.ts @@ -358,11 +343,11 @@ export const whoami = tool( if (!identity) { return "No authenticated caller on this run."; } - return `Signed in as ${identity.actor.id}`; + return `Signed in as ${identity.user.id}`; }, { name: "whoami", - description: "Return the authenticated actor id for this run.", + description: "Return the authenticated user id for this run.", schema: z.object({}), }, ); @@ -382,7 +367,7 @@ from managed_deepagents import ManagedDeepAgentRuntime def audit_middleware(): @before_model def audit(state: AgentState, runtime: ManagedDeepAgentRuntime) -> dict | None: - user = runtime.identity["actor"]["id"] if runtime.identity else "anonymous" + user = runtime.identity["user"]["id"] if runtime.identity else "anonymous" print(f"[audit] {user} model call with {len(state['messages'])} messages") return None @@ -397,7 +382,7 @@ export function auditMiddleware() { return createMiddleware({ name: "audit", beforeModel: (state, runtime: ManagedDeepAgentRuntime) => { - const user = runtime.identity?.actor.id ?? "anonymous"; + const user = runtime.identity?.user.id ?? "anonymous"; console.log( `[audit] ${user} model call with ${state.messages.length} messages` ); @@ -409,85 +394,154 @@ export function auditMiddleware() { -Prefer `runtime.identity` over client-supplied configurable keys for actor or tenant ids. For other per-run values such as feature flags, use normal LangChain runtime context. +Prefer `runtime.identity` over client-supplied configurable keys for user or organization ids. For other per-run values such as feature flags, use normal LangChain runtime context. ## Customize scoping -Presets cover the common cases. To customize, set `scoping` explicitly: +The common shapes cover most cases. To customize, set `scope` per axis: | Axis | Values | Meaning | | --- | --- | --- | -| `threads` | `actor`, `channel`, `tenant` | Who can open or resume the conversation | -| `memory` | `actor`, `tenant`, `agent`, `none` | Which Context Hub memory slice is remounted for the run | -| `credentials` | `agent`, `actor`, `none`, `custom` | Whose credentials downstream calls use | +| `threads` | `user`, `conversation`, `organization` | Who can open or resume the conversation | +| `memory` | `user`, `organization`, `agent`, `none` | Which Context Hub memory slice is remounted for the run | +| `credentials` | `user`, `agent`, `none`, `custom` | Whose credentials downstream calls use | -If `tenancy` is `"single"`, do not set any scoping axis to `"tenant"`, there is no tenant to scope by. If a request is missing the actor or tenant id that scoping needs, Managed Deep Agents rejects it with 403 instead of falling back to shared data. +Do not set any scoping axis to `"organization"` when organizations are optional, there may be no organization to scope by. If a request is missing the user or organization id that scoping needs, Managed Deep Agents rejects it with 403 instead of falling back to shared data. For how memory paths remount under each scope, see [Scope memory with identity](/langsmith/managed-deep-agents-memory#scope-memory-with-identity). -### Custom downstream credentials +### Downstream credentials -Use `scoping.credentials: "custom"` when your application can securely obtain a per-actor credential for a downstream target. Provide a `resolve` function; tools then call `runtime.credentials.for(target)` to obtain the headers for that request. Resolved credentials are kept in memory and are never written to thread state or traces. +Declare `credentials` when downstream calls need more than an agent-wide token. `credentials` accepts one resolver or a map keyed by target name, so one deployment can integrate several platforms. Providing any resolver puts the credentials axis into `custom` mode; tools then call `runtime.credentials.for(target)` to obtain the headers for that request. Resolved credentials are kept in memory and are never written to thread state or traces. The token that proves a caller's identity is not automatically a credential for downstream APIs. For example, a Supabase access token lets Managed Deep Agents identify the caller, but it is not a GitHub API token. Your backend or credential service must hold (and, when needed, refresh) the caller's separately authorized GitHub credential. -The following shape lets a user sign in through Supabase and open GitHub pull requests as themselves. After the user has separately authorized GitHub, your application stores the GitHub grant keyed by the Supabase user id. `getGitHubAccessToken` is application code: it looks up and refreshes that grant in your server-side credential store. +For GitHub, the first-party `credentials.github` resolver chains token sources per intent. When the chain reads `"user"` tokens, Connect-with-GitHub OAuth routes mount automatically—there is nothing else to declare: + + + +```python identity.py +import os + +from managed_deepagents import auth, credentials, define_identity + +identity = define_identity( + auth=auth.supabase(project_ref="your-project-ref"), + credentials={ + "github": credentials.github( + read=["user", "pat"], + write=["user"], + pat=os.environ.get("GITHUB_TOKEN") or os.environ.get("GITHUB_PAT"), + ), + }, +) +``` ```ts identity.ts -import { defineIdentity, providers } from "managed-deepagents"; -import { getGitHubAccessToken } from "./github-credentials.js"; +import { auth, credentials, defineIdentity } from "managed-deepagents"; export const identity = defineIdentity({ - ingress: { - http: { - mode: "validated_token", - providers: [providers.supabase({ projectRef: "your-project-ref" })], - }, - }, - tenancy: "single", - scoping: { - threads: "actor", - memory: "actor", - credentials: "custom", - }, + auth: auth.supabase({ projectRef: "your-project-ref" }), credentials: { - async resolve({ identity, target }) { - if (target.name !== "github") { - throw new Error(`No credentials configured for ${target.name}.`); - } - - const credential = await getGitHubAccessToken(identity.actor.id); - if (!credential) { - throw new Error("Connect GitHub before using GitHub tools."); - } - - return { - headers: { Authorization: `Bearer ${credential.token}` }, - expiresAt: credential.expiresAt.toISOString(), - }; - }, + github: credentials.github({ + read: ["user", "pat"], + write: ["user"], + pat: process.env.GITHUB_TOKEN ?? process.env.GITHUB_PAT, + }), }, }); ``` + + In a GitHub tool, request the headers with `await runtime.credentials.for({ kind: "connection", name: "github", intent: "write" })` and pass them to your GitHub client. +For other platforms, supply a `resolve` function yourself. The following shape lets a user open pull requests as themselves after your application has stored their GitHub grant. `getGitHubAccessToken` is application code: it looks up and refreshes that grant in your server-side credential store. + +```ts identity.ts +import { auth, defineIdentity } from "managed-deepagents"; +import { getGitHubAccessToken } from "./github-credentials.js"; + +export const identity = defineIdentity({ + auth: auth.supabase({ projectRef: "your-project-ref" }), + credentials: { + github: { + async resolve({ identity, target }) { + const credential = await getGitHubAccessToken(identity.user.id); + if (!credential) { + throw new Error("Connect GitHub before using GitHub tools."); + } + + return { + headers: { Authorization: `Bearer ${credential.token}` }, + expiresAt: credential.expiresAt.toISOString(), + }; + }, + }, + }, +}); +``` + To expose LangSmith capabilities to browsers or other untrusted callers, add a [LangSmith connector](/langsmith/managed-deep-agents-connectors/langsmith). It requires identity so capability routes can resolve the caller and prove ownership before calling LangSmith server-side. +### Connect-with-X routes + +Connect-with-X binds an MDA user to an external account via OAuth—either to link a channel identity (Slack) or to populate a per-user credential vault (GitHub). Routes are **inferred** from the rest of the declaration on user-scoped deployments: + +- Connect-with-GitHub mounts when a credential chain reads `"user"` tokens. +- Connect-with-Slack mounts when a [Slack channel](/langsmith/managed-deep-agents-channels/slack) is declared. + +Declare `connect` only to add providers the inference cannot see, such as a custom OAuth provider: + + + +```python identity.py +from managed_deepagents import connect, define_identity + +identity = define_identity( + connect=[connect.github(), connect.slack()], +) +``` + +```ts identity.ts +import { connect, defineIdentity } from "managed-deepagents"; + +export const identity = defineIdentity({ + connect: [connect.github(), connect.slack()], +}); +``` + + + ## Provider setup guides -These guides cover the built-in providers for [validated token](#validated-token-browser-direct) ingress. Use one provider, or combine them as in the example in that section. +The `auth` namespace ships providers for the common identity providers: + +| Provider | Factory | Verification | +| --- | --- | --- | +| Auth0 | `auth.auth0({ domain, audience? })` | JWKS | +| Clerk | `auth.clerk({ domain })` | JWKS | +| Okta | `auth.okta({ domain, audience? })` | JWKS | +| Amazon Cognito | `auth.cognito({ userPoolId, region })` | JWKS | +| Microsoft Entra | `auth.entra({ tenantId, audience? })` | JWKS | +| Google | `auth.google({ audience? })` | JWKS | +| Supabase | `auth.supabase(...)` | JWKS (or legacy introspection) | +| Any OIDC IdP | `auth.oidc({ issuer, audience? })` | OIDC discovery | +| GitHub | `auth.github()` | Opaque token introspection | +| Guest | `auth.guest(...)` | MDA-signed tokens | + +Each factory returns a plain provider object, so spread it to override the claim mapping (`user`, `organization`, `groups`, `email`) when the token's claims do not match the defaults. The tabs below cover the providers that need extra setup. Use one provider, or combine them as in the [validated token example](#validated-token-browser-direct). - Anonymous visitors get a short-lived, actor-scoped session without signing in. Managed Deep Agents signs guest tokens with `MDA_GUEST_SIGNING_KEY` (HS256) and maps `sub` → actor. + Anonymous visitors get a short-lived, user-scoped session without signing in. Managed Deep Agents signs guest tokens with `MDA_GUEST_SIGNING_KEY` (HS256) and maps `sub` → user. | Option | Required | Description | | --- | --- | --- | | `ttl` | No | Token lifetime (for example `"24h"`) | - | `actorPrefix` / `actor_prefix` | No | Prefix for generated actor ids (for example `"guest:"`) | + | `userPrefix` / `user_prefix` | No | Prefix for generated user ids (for example `"guest:"`) | Guest is usually combined with another IdP, as in the [validated token example](#validated-token-browser-direct). @@ -550,14 +604,14 @@ These guides cover the built-in providers for [validated token](#validated-token - For browser apps, proxy guest issuance through your own backend and store the token in an `httpOnly` cookie. That keeps the same guest actor across reloads until `exp` and lets you handle rate limits before calling the deployment. + For browser apps, proxy guest issuance through your own backend and store the token in an `httpOnly` cookie. That keeps the same guest user across reloads until `exp` and lets you handle rate limits before calling the deployment. - Reclaim a token when the current one is expired or missing. While a token is still valid, reuse it so the guest keeps the same actor id, threads, and memory scope for the token lifetime. + Reclaim a token when the current one is expired or missing. While a token is still valid, reuse it so the guest keeps the same user id, threads, and memory scope for the token lifetime. - JWKS by default (asymmetric JWTs). Maps `sub` → actor. Pass only one of `projectRef` or `url`. + JWKS by default (asymmetric JWTs). Maps `sub` → user. Pass only one of `projectRef` or `url`. | Option | Required | Description | | --- | --- | --- | @@ -565,7 +619,7 @@ These guides cover the built-in providers for [validated token](#validated-token | `url` | One of `projectRef` or `url` | Project URL or custom auth domain | | `introspect` | No | `true` for legacy HS256 projects that need `/auth/v1/user` | - Use `providers.supabase(...)` alone, or combine it with guest as in the [validated token example](#validated-token-browser-direct). + Use `auth.supabase(...)` alone, or combine it with guest as in the [validated token example](#validated-token-browser-direct). After sign-in, send `session.access_token` from [@supabase/supabase-js](https://supabase.com/docs/reference/javascript/auth-getsession). See also [Supabase Auth](https://supabase.com/docs/guides/auth) and [JWT signing keys](https://supabase.com/docs/guides/auth/signing-keys). @@ -573,42 +627,27 @@ These guides cover the built-in providers for [validated token](#validated-token - Opaque token introspection via `GET https://api.github.com/user`. Maps `login` → actor, `email` → email. `providers.github()` takes no options. + Opaque token introspection via `GET https://api.github.com/user`. Maps `login` → user, `email` → email. `auth.github()` takes no options. + ```python identity.py - from managed_deepagents import define_identity, providers - - identity = define_identity.preset( - "internal-tool", - { - "ingress": { - "http": { - "mode": "validated_token", - "providers": [providers.github()], - } - } - }, - ) + from managed_deepagents import auth, define_identity + + identity = define_identity(auth=auth.github()) ``` ```ts identity.ts - import { defineIdentity, providers } from "managed-deepagents"; - - export const identity = defineIdentity.preset("internal-tool", { - ingress: { - http: { - mode: "validated_token", - providers: [providers.github()], - }, - }, - }); + import { auth, defineIdentity } from "managed-deepagents"; + + export const identity = defineIdentity({ auth: auth.github() }); ``` + Complete a [GitHub OAuth App](https://docs.github.com/en/apps/oauth-apps/building-oauth-apps) sign-in flow, then send the **user access token**. Do not send OAuth client secrets to the deployment. See also [Authorizing OAuth apps](https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/authorizing-oauth-apps) and [Get the authenticated user](https://docs.github.com/en/rest/users/users#get-the-authenticated-user). - For production, prefer [trusted backend](#trusted-backend-recommended-default) ingress: keep the GitHub token on your API, and proxy with `X-MDA-Ingress-Secret` + `X-MDA-Actor-Id` (for example the GitHub `login`). + For production, prefer [backend](#backend-recommended-default) auth: keep the GitHub token on your API, and proxy with `X-MDA-Ingress-Secret` + `X-MDA-User-Id` (for example the GitHub `login`). @@ -616,13 +655,13 @@ These guides cover the built-in providers for [validated token](#validated-token -Identity misconfiguration usually surfaces as 401 (auth) or 403 (store/thread scope) during local Studio or the first authenticated request. Confirm the matching secret is present and that trusted-backend proxies attach the reserved headers. +Identity misconfiguration usually surfaces as 401 (auth) or 403 (store/thread scope) during local Studio or the first authenticated request. Confirm the matching secret is present and that backend proxies attach the reserved headers. ## Next steps - See how identity remounts per-actor or per-tenant memory. + See how identity remounts per-user or per-organization memory. Read `runtime.identity` from authored tools. @@ -631,13 +670,13 @@ Identity misconfiguration usually surfaces as 401 (auth) or 403 (store/thread sc Supply `identity.json` fixtures for Harbor tasks when identity is declared. - Run cron agents, including the `service` preset shape. + Run cron agents, including the `agent` scope shape. Expose constrained LangSmith capabilities to untrusted callers. - Receive Slack Events with shared-bot or Connect-with-Slack linking. + Receive Slack Events with a shared bot or Connect-with-Slack linking. See how compile and deploy wire auth into the runtime. diff --git a/src/langsmith/managed-deep-agents-memory.mdx b/src/langsmith/managed-deep-agents-memory.mdx index 0ab19d3554..0336eed99a 100644 --- a/src/langsmith/managed-deep-agents-memory.mdx +++ b/src/langsmith/managed-deep-agents-memory.mdx @@ -9,7 +9,7 @@ import ManagedDeepAgentsTestAndDeploy from '/snippets/langsmith/managed-deep-age Managed Deep Agents gives every deployment durable long-term memory: agents remember each user's preferences and context across threads and sessions, without you building a persistence layer. -Memory is backed by [Context Hub](/langsmith/use-the-context-hub), where the agent reads and updates files under `/memories/user/`. With [identity](/langsmith/managed-deep-agents-identity), memory is remounted per actor or tenant so callers (the users, service accounts, or clients that trigger agent runs) cannot see each other's private state. +Memory is backed by [Context Hub](/langsmith/use-the-context-hub), where the agent reads and updates files under `/memories/user/`. With [identity](/langsmith/managed-deep-agents-identity), memory is remounted per user or organization so callers (the users, service accounts, or clients that trigger agent runs) cannot see each other's private state. @@ -36,10 +36,10 @@ The agent sees the following paths at runtime: | --- | --- | --- | | `/instructions.md` | Hub `instructions.md` | Read-only | | `/skills/**` | Hub `skills/**` | Read-only | -| `/memories/user/**` | One remounted Hub slice (for example `memories/`) | Read/write | +| `/memories/user/**` | One remounted Hub slice (for example `memories/`) | Read/write | | `/memories/org/**` | Hub `org-memory/**` (if present) | Read-only | -A *memory slice* is a subdirectory within the Context Hub `memories/` tree that belongs to one scope: a single actor (`memories/`), a tenant (`memories/`), or the shared agent (`memories/agent`). At runtime, one slice is remounted as `/memories/user/`, so the agent never sees a multi-user directory listing under `/memories/`. +A *memory slice* is a subdirectory within the Context Hub `memories/` tree that belongs to one scope: a single user (`memories/`), an organization (`memories/`), or the shared agent (`memories/agent`). At runtime, one slice is remounted as `/memories/user/`, so the agent never sees a multi-user directory listing under `/memories/`. ## Hot and cold memory @@ -88,39 +88,39 @@ After a successful write, a **new thread** for the same caller should recall the Without identity, every caller shares the same agent memory slice (`memories/agent` in Context Hub, remounted as `/memories/user`). -With identity, `scoping.memory` chooses which Hub subdirectory is remounted: +With identity, `scope.memory` chooses which Hub subdirectory is remounted: -| `scoping.memory` | Hub path remounted as `/memories/user` | +| `scope.memory` | Hub path remounted as `/memories/user` | | --- | --- | -| `actor` (single-tenant) | `memories/` | -| `actor` (multi-tenant) | `memories//` | -| `tenant` | `memories/` | +| `user` (organizations optional) | `memories/` | +| `user` (organizations required) | `memories//` | +| `organization` | `memories/` | | `agent` | `memories/agent` | | `none` | `/memories/user/` is not mounted, and hot memory is not injected | -Isolation is enforced: a run only sees its remounted tree. Sibling actor or tenant trees are unreachable. +Isolation is enforced: a run only sees its remounted tree. Sibling user or organization trees are unreachable. -Presets such as `private-assistant` and `internal-tool` set `memory: "actor"`. The `service` preset uses shared `agent` memory. For more information about presets and ingress, see [Identity](/langsmith/managed-deep-agents-identity). +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). ```python identity.py from managed_deepagents import define_identity -identity = define_identity.preset("private-assistant") -# scoping.memory == "actor" +identity = define_identity() +# scope.memory == "user" ``` ```ts identity.ts import { defineIdentity } from "managed-deepagents"; -export const identity = defineIdentity.preset("private-assistant"); -// scoping.memory === "actor" +export const identity = defineIdentity(); +// scope.memory === "user" ``` -When an actor or tenant interacts with the agent for the first time, the runtime creates their `/memories/user/AGENTS.md` file automatically. It copies from a project-defined seed file if one exists, or falls back to a built-in default template. This ensures the agent has its memory instructions available from the first turn, rather than starting with an empty file. +When a user or organization interacts with the agent for the first time, the runtime creates their `/memories/user/AGENTS.md` file automatically. It copies from a project-defined seed file if one exists, or falls back to a built-in default template. This ensures the agent has its memory instructions available from the first turn, rather than starting with an empty file. ## Org memory (read-only) @@ -134,11 +134,11 @@ Optional org-wide facts live under Context Hub `org-memory/` and mount at `/memo - Seeds `memories/agent/AGENTS.md` and `org-memory/AGENTS.md` when missing - Preserves existing memory files across rebuilds -Actor-scoped local runs remount `memories//` as `/memories/user` the same way as deploy. When you update the Managed Deep Agents SDK in your project, rebuild so `.mda/build/__runtime__` picks up the new runtime. The runtime is copied at compile time, so SDK changes do not take effect until you rebuild. +User-scoped local runs remount `memories//` as `/memories/user` the same way as deploy. When you update the Managed Deep Agents SDK in your project, rebuild so `.mda/build/__runtime__` picks up the new runtime. The runtime is copied at compile time, so SDK changes do not take effect until you rebuild. ## Disable managed memory -Use `disableMemory` for stateless agents or when you manage persistence externally. Set `disableMemory` / `disable_memory` on the agent definition to skip hot injection and the `/memories/user` memory wiring. Identity `scoping.memory: "none"` also disables the mount. +Use `disableMemory` for stateless agents or when you manage persistence externally. Set `disableMemory` / `disable_memory` on the agent definition to skip hot injection and the `/memories/user` memory wiring. Identity `scope.memory: "none"` also disables the mount. @@ -181,7 +181,7 @@ If the agent claims to remember something but the fact is missing in a new threa If hot memory at `/memories/user/AGENTS.md` grows too large, it consumes tokens from every request's system prompt. Move detailed content to cold files under `/memories/user/archive/` and keep only preferences and pointers in hot memory. -This is a misconfiguration, not a platform issue. Verify that `scoping.memory` is set to `actor` or `tenant` (not `agent`). Check that the identity declaration is present and that the ingress mode correctly resolves the caller. Inspect `runtime.identity` in traces to confirm the resolved actor and tenant ids. For more information, see [Identity](/langsmith/managed-deep-agents-identity). +This is a misconfiguration, not a platform issue. Verify that `scope.memory` is set to `user` or `organization` (not `agent`). Check that the identity declaration is present and that the auth mode correctly resolves the caller. Inspect `runtime.identity` in traces to confirm the resolved user and organization ids. For more information, see [Identity](/langsmith/managed-deep-agents-identity). The runtime creates `/memories/user/AGENTS.md` from the seed template only when the file does not already exist. If a user reports overwritten content, the file was likely absent when the slice was first accessed, so the runtime seeded a fresh copy. Deploy never overwrites existing `memories/**` files. @@ -191,7 +191,7 @@ The runtime creates `/memories/user/AGENTS.md` from the seed template only when - Partition memory per actor or tenant with `scoping.memory`. + Partition memory per user or organization with `scope.memory`. See how Context Hub, threads, and deploy sync fit together. diff --git a/src/langsmith/managed-deep-agents-quickstart.mdx b/src/langsmith/managed-deep-agents-quickstart.mdx index b27e890340..2e581fbf4c 100644 --- a/src/langsmith/managed-deep-agents-quickstart.mdx +++ b/src/langsmith/managed-deep-agents-quickstart.mdx @@ -83,7 +83,7 @@ OPENAI_API_KEY= `LANGSMITH_API_KEY` authenticates `mda deploy`. Provider keys, MCP tokens, database URLs, and other non-reserved `.env` values are sent to the hosted deployment as secrets when you deploy. The `.env` file itself is not uploaded in the source archive. -The CLI targets US LangSmith Cloud by default. To deploy with an organization-scoped key, set `LANGSMITH_TENANT_ID` in `.env` or pass `--tenant-id` to `mda deploy`. +The CLI targets US LangSmith Cloud by default. To deploy with an organization-scoped key, set `LANGCHAIN_WORKSPACE_ID` in `.env` or pass `--workspace-id` to `mda deploy`. If a request returns 401 or 403, confirm the key belongs to a workspace with beta access. @@ -177,7 +177,7 @@ On success, the CLI prints the LangSmith deployment dashboard URL: ```text Deployment live Deployment dashboard -https://smith.langchain.com/o//host/deployments/ +https://smith.langchain.com/o//host/deployments/ Deployed 'research-assistant' to LangSmith. ``` diff --git a/src/langsmith/managed-deep-agents-tools.mdx b/src/langsmith/managed-deep-agents-tools.mdx index 57ccdc3791..61645568d1 100644 --- a/src/langsmith/managed-deep-agents-tools.mdx +++ b/src/langsmith/managed-deep-agents-tools.mdx @@ -106,7 +106,7 @@ Use clear, unique tool names. MCP connector tools are appended after authored to Tools can read deployment secrets from environment variables. Put local values in `.env` for `mda dev`; `mda deploy` forwards non-reserved `.env` values as hosted deployment secrets. -When the project declares [identity](/langsmith/managed-deep-agents-identity), tools and middleware receive a frozen `runtime.identity` envelope for the authenticated caller. Prefer that over client-supplied configurable keys for actor or tenant ids. +When the project declares [identity](/langsmith/managed-deep-agents-identity), tools and middleware receive a frozen `runtime.identity` envelope for the authenticated caller. Prefer that over client-supplied configurable keys for user or organization ids. For other per-run values such as request metadata or feature flags, use the normal LangChain runtime context patterns for tools. See [how to access context from within your tools](/oss/langchain/tools#access-context).