diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 401cf08e1..f3274c8b5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -59,7 +59,8 @@ jobs: e2e: name: End-to-end tests - runs-on: ubuntu-latest + # Match the Ubuntu version supported by the locked Playwright browsers. + runs-on: ubuntu-24.04 steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/update-screenshots.yml b/.github/workflows/update-screenshots.yml index d14b945c6..8e91d7391 100644 --- a/.github/workflows/update-screenshots.yml +++ b/.github/workflows/update-screenshots.yml @@ -16,7 +16,8 @@ permissions: jobs: screenshots: name: Capture and open PR - runs-on: ubuntu-latest + # Match the Ubuntu version supported by the locked Playwright browsers. + runs-on: ubuntu-24.04 environment: Production steps: - uses: actions/checkout@v4 diff --git a/content/docs/configuration/librechat_yaml/object_structure/agents.mdx b/content/docs/configuration/librechat_yaml/object_structure/agents.mdx index 4dc7e1fd0..0bcfbfcb6 100644 --- a/content/docs/configuration/librechat_yaml/object_structure/agents.mdx +++ b/content/docs/configuration/librechat_yaml/object_structure/agents.mdx @@ -1497,10 +1497,70 @@ Tokens must be unexpired and present a single, non-conflicting client identifier Unknown management-auth fields, duplicate client IDs, malformed user or tenant IDs, and enabled authentication without a client binding fail configuration validation. See [Agents API - Agent Management](/docs/features/agents_api#agent-management-api) for routes and examples. +## channels + +For organization workspaces, `channels.maxBotManagers` limits the number of explicitly granted bot managers per app/workspace installation (default `50`, range `1`–`1000`). Administrators grant these users through the workspace’s **Manage access** button (**Manage bot access** dialog); bot management does not grant Agent sharing or installation-administration permissions. + +Configures the opt-in [Agent Channels integration](/docs/features/agent_channels). Enabled background Responses and a separately running gateway are required. The LibreChat source includes the standalone TypeScript Slack gateway in `packages/channel-gateway`; this setting does not start it or enable execution permissions. + +| Option | Default | Accepted values / purpose | +| --- | --- | --- | +| `enabled` | `false` | Enable channel setup and service routes | +| `transport` | `socket_mode` | Select `socket_mode` or `webhook` ingress; manual connections generate a matching manifest and the gateway runs separately | +| `gatewayUrl` | Omitted | Required HTTPS base URL for generating webhook callback URLs; not needed for Socket Mode | +| `nativeSessions` | `false` | Enable optional Slack native Agent status emission after app setup | +| `lifecycleReactions` | `false` | Add optional processing/completion reactions | +| `signatureToleranceSeconds` | `300` | Webhook signature timestamp window; integer from 1 to 300 seconds | +| `socketReconnectDelayMs` | `5000` | Socket reconnect delay; integer from 100 to 60000 milliseconds | +| `ticketLifetimeMs` | `600000` | Link ticket lifetime, `60000`–`3600000` ms | +| `linkLifetimeMs` | `2592000000` | Accepted link lifetime, `60000`–`7776000000` ms | +| `leaseMs` | `60000` | Run lease, `10000`–`300000` ms | +| `responseCredentialLifetimeMs` | `86400000` | Bound execution credential lifetime, `60000`–`604800000` ms | +| `artifactMaxBytes` | `10485760` | Per-artifact limit, `1`–`52428800` bytes | +| `artifactMaxFiles` | `5` | Artifacts per run, `1`–`20` | +| `artifactReadTimeoutMs` | `30000` | Shared artifact-operation budget, `1000`–`60000` ms; not a separate timeout for each file | +| `artifactAnswerFileThresholdBytes` | `6000` | Create new plain-text answer files only above this UTF-8 byte length, `0`–`52428800`; fenced answers also qualify | + +`slackApp` enables organization-owned apps and sets installation/runtime policy. Set `channels.enabled: true`, `transport: webhook` and `slackApp.enabled: true`. Each registered app supports one active workspace. Organization admins register an existing Slack app through **Settings → Account → Slack → Set up/Manage → Add Slack app**, with an optional friendly name and app/client credentials; credentials are encrypted in the database and are not YAML or environment settings. The deployment opt-in `SLACK_WORKSPACES_ENABLED=true` enables these policies and background Responses while preserving other YAML settings. Without the opt-in, YAML remains authoritative. `SLACK_GATEWAY_URL` supplies the public gateway URL. See [Organization workspaces](/docs/features/agent_channels#organization-workspaces) for deployment and UI steps. + +| `slackApp` option | Default | Purpose | +| --- | --- | --- | +| `enabled` | `false` | Enable organization app registration and workspace installation | +| `flowLifetimeMs` | `600000` | Browser install lifetime, `60000`–`3600000` ms | +| `requestTimeoutMs` | `10000` | Slack installation/verification request deadline, `1000`–`30000` ms | +| `maxResponseBytes` | `65536` | Maximum Slack response bytes, `1024`–`262144` | +| `gateway.ingressConcurrency` | `32` | Concurrent shared ingress requests, `1`–`128` | +| `gateway.workerConcurrency` | `4` | Concurrent durable worker turns per process, `1`–`32` | +| `gateway.discoveryBatchSize` | `32` | Shared discovery page, `1`–`100`, also capped by `maxMetadataPageSize` | +| `gateway.pollIntervalMs` | `1000` | Discovery polling interval, `100`–`60000` ms | + +Workspace-bound Agent connections require personal identities. Shared gateway mode does not mean the manual owner-only `shared` credential mode; it shares transport and workers across installations, while every member retains their own execution identity. + +The artifact budget covers catalog queries, storage acquisition/consumption and final authorization. Limits resolution is capped at the schema maximum of 60 seconds; once resolved, its elapsed time counts toward the configured budget. Expiry omits artifacts rather than returning files without current authorization. The threshold applies to new answer-file projections; existing saved descriptors remain bound to their bytes and require current owner, tenant, retention and maximum-size checks. + +`cleanup` accepts `batchSize` (`25`, range `1`–`100`), `intervalMs` (`1000`, range `1000`–`60000`), and `maxIdleIntervalMs` (`30000`, range `1000`–`300000`). + +`feedback` accepts `maxConflictRetries` (`5`, range `1`–`20`), `exportTimeoutMs` (`10000`, range `1000`–`60000`), `exportLeaseMs` (`60000`, range `60000`–`300000`), `exportRetryMs` (`5000`, range `1000`–`300000`), and the same `batchSize`, `intervalMs`, and `maxIdleIntervalMs` defaults/ranges as `cleanup`. + +The gateway service boundary requires the server-side `CHANNEL_SERVICE_TOKEN`. Do not expose it to browsers or use it as a general Agent execution credential. Channel credential mode (`shared` or `per_user`) belongs to the channel record, not this deployment block. Both modes require explicit current actor linking; `shared` restricts linking to the channel creator's account rather than granting all external participants that account's privileges. + +The gateway reads this versioned policy from LibreChat before selecting ingress. `SLACK_TRANSPORT` is optional and asserts the selected mode; a mismatch or unsupported policy prevents startup. Native sessions, lifecycle reactions and signature policy are configured here rather than through gateway process flags. Gateway runtime settings, including its tenant/connection IDs, app-level Socket Mode token and optional `APP_TITLE` label, are documented in [Run the Slack gateway](/docs/features/agent_channels#run-the-slack-gateway). + ## remoteApi Configuration for Remote Agent API authentication. Controls how external services authenticate when calling the Agents API endpoints. +### remoteApi.background + +Enables the opt-in [background Responses lifecycle](/docs/features/background_responses). It does not enable Remote Agents permissions or human approval by itself. + +| Option | Type | Default | Meaning | +| --- | --- | --- | --- | +| `enabled` | Boolean | `false` | Accept durable background Responses requests | +| `acknowledgementTimeoutMs` | Integer | `10000` | Worker admission acknowledgement timeout, from `100` through `60000` ms; not the model run timeout | + +Background requests require an Agent API key, stored responses, and non-streaming submission. Human-action resume requires durable MongoDB checkpoints and the corresponding existing approval/question policy. Set `DOMAIN_CLIENT` to the trusted public owner-review URL origin. + ### remoteApi.auth +Agent Channels are a beta integration and are disabled by default. The standalone TypeScript Slack gateway lives in `packages/channel-gateway` in the LibreChat repository. Enabling Channels does not start that process or install a Slack app. + + +Agent Channels connect external messaging events to [background Agent Responses](/docs/features/background_responses). Channel routes manage identity, admission, leases, delivery receipts, artifacts, and feedback. They are **not an alternative model-execution endpoint**: a compatible gateway submits execution through `/api/agents/v1/responses` using a server-provisioned, bound credential. + +## Configuration and prerequisites + +Choose organization workspaces for UI-managed, organization-owned Slack apps and workspace installations. The YAML example below is the manual single-connection setup; the shared-workspace deployment options also enable Channels and background Responses automatically. + +```yaml filename="endpoints / agents" +endpoints: + agents: + remoteApi: + background: + enabled: true + channels: + enabled: true + transport: socket_mode + nativeSessions: false + lifecycleReactions: false + signatureToleranceSeconds: 300 + socketReconnectDelayMs: 5000 +``` + +Both enablement settings default to `false`. Users need both Agent use and Remote Agents use permissions. Channel setup also requires Agent sharing permission or existing administrator management access; sharing access does not require permission to edit the Agent. Configure `DOMAIN_CLIENT` for trusted owner-review and account-link URLs. Configure a strong `CHANNEL_SERVICE_TOKEN` for the gateway-to-LibreChat service boundary; keep it server-side and distribute it only to a trusted integration process over a secure channel. It is not a browser token or a general Agent API key. + +The `transport` option accepts `socket_mode` (default) or `webhook`. Manual connections generate a matching Slack manifest; organization workspaces use the webhook setup below. Selecting a transport does not deploy a receiver. Webhook manifests require an HTTPS `gatewayUrl` without credentials, a query, or a fragment. The gateway loads this policy from LibreChat before selecting ingress. `SLACK_TRANSPORT` is an optional deployment assertion; if supplied, it must match the server-selected mode. Missing or unsupported gateway policy prevents startup. + +### Database requirement + +Durable Channels and stored background Responses require MongoDB transactions. Use a MongoDB replica set or another verified transaction-capable deployment. Standalone MongoDB and FerretDB 2.7 can run ordinary LibreChat, but cannot enable this durable feature. Unsupported channel writes return `transactions_unavailable`; the Agent builder explains the setup requirement. Transactional reads use the primary even when the connection normally prefers secondary reads. The database must also support the required indexes, including partial indexes; transaction support by itself is not sufficient. [Amazon DocumentDB 4.0 lacks partial-index support](https://docs.aws.amazon.com/documentdb/latest/devguide/indexes-single-field.html) and cannot run this gateway. Other MongoDB-compatible engines need their own compatibility validation. + +API-key authentication must remain enabled for the gateway's bound execution credential. Linking does not grant Remote Agents permissions, Agent access, new tools or offline access to connected services. + +See the [channel configuration reference](/docs/configuration/librechat_yaml/object_structure/agents#channels) for limits and defaults. + +## Organization workspaces + +A deployment operator enables the shared gateway once, without creating Slack apps or handling customer credentials. Organization administrators register their own Slack apps and connect workspaces in LibreChat; members link their personal accounts in Slack. Installing an app does not give members access to an Agent or its tools. In a tenant-aware deployment, installations and routing belong to the active tenant. One app/workspace installation has one tenant owner; another tenant cannot claim an active installation. + +### One-time operator setup + +1. Configure transaction-capable MongoDB and a private `CHANNEL_SERVICE_TOKEN` shared by the API and gateway. Keep API-key authentication enabled for bound execution and retain normal Agent and Remote Agents permission controls. +2. Configure public HTTPS `DOMAIN_SERVER` and `DOMAIN_CLIENT`, replacing the localhost defaults in `.env`. Both are required and must have no surrounding whitespace. Include the application base path when present. Set `SLACK_GATEWAY_URL` to the public gateway origin; it must also use public HTTPS. The shared deployment opt-in rejects local addresses. +3. Route `/slack/events/` and `/slack/interactions/`, including their organization/app suffixes, to the gateway. Its container listens on port 8080; the shared Compose overlay publishes it at `127.0.0.1:3081` for a host-level HTTPS proxy. Route the application and OAuth callback to LibreChat. Keep gateway health and metrics private. No Slack dashboard access, app credentials or per-organization infrastructure settings are required. + +For Docker Compose, use the normal uncommitted `.env`: + +```dotenv +SLACK_GATEWAY_URL=https://slack.example.com +CHANNEL_SERVICE_TOKEN= +DOMAIN_SERVER=https://chat.example.com +DOMAIN_CLIENT=https://chat.example.com +LIBRECHAT_IMAGE_TAG= +``` + +From a fresh LibreChat setup with `.env` and `librechat.yaml` prepared, start the release-matched application and shared gateway: + +```sh +docker compose -f docker-compose.yml -f docker-compose.channels.yml \ + -f docker-compose.slack-workspaces.yml -f docker-compose.mongo-replica.yml \ + --profile channels up -d api meilisearch channel-gateway +``` + +The replica overlay is for a **fresh local database**; read [Existing local data](#existing-local-data) before converting an existing installation. Omit that overlay when you already use a verified transaction-capable external database and supply its normal connection configuration. Use the same Compose project and files for subsequent commands. This does not configure a public reverse proxy or a trusted certificate. A host-level proxy forwards the two Slack paths to `http://127.0.0.1:3081`, preserving the organization/app suffixes. Set `CHANNEL_GATEWAY_PORT` in `.env` to change that loopback port. A proxy in the same Compose network can instead forward to `http://channel-gateway:8080`. + +The shared overlay supplies `SLACK_WORKSPACES_ENABLED=true`, which enables webhook Channels, shared app installation and stored background Responses while retaining unrelated YAML policy, limits and explicit API authentication settings. Slack app credentials are entered by organization admins through the UI and stored encrypted; they are not deployment environment variables or YAML fields. With the flag unset or false, YAML continues to control the feature. Choosing shared setup changes the deployment's channel transport to webhook; existing bound connections must use matching webhook configuration. Manual Socket Mode remains available when using the manual setup instead. + +No `CHANNEL_ID`, `CHANNEL_TENANT_ID` or app-level `SLACK_APP_TOKEN` is needed in shared mode. The gateway receives its service token and private runtime policy; app client credentials stay in LibreChat, and installed bot tokens are encrypted in the database. For an unreleased source checkout, add `docker-compose.channels-build.yml` and use `up -d --build` rather than expecting an unpublished release image. `npm run backend:dev` still starts only the application; for a source gateway process, omit the bound IDs and app-level token and provide the internal API URL and service token. + +### Register an organization’s Slack app + +Complete [operator setup](#one-time-operator-setup) first. The gateway must be running and reachable over public HTTPS before Slack can verify its callback. A Slack app is the bot's identity and credentials; its workspace installation is the grant that lets it operate in a particular Slack workspace. LibreChat supports **one active workspace per registered app**. Use separate Slack apps for additional workspaces. + +1. Open [Your Apps](https://api.slack.com/apps) in Slack and create an app from scratch (**Pick features** in the current dashboard, or **Blank app** in Slack’s quickstart). Give it a name and select the workspace where you will manage it. In the **Pick features** wizard, continue without adding scopes or events, then select **Create → Create Anyway**. Configure those features after registering the app in LibreChat below. You can instead use an existing app your organization controls. Do not install it yet. +2. In LibreChat, open **Settings → Account → Slack → Set up** (or **Manage**), then **Add Slack app**. Copy **App ID**, **Client ID**, **Client Secret**, and **Signing Secret** from that app's **Basic Information → App Credentials**. Select **Show** beside each secret before copying; the masked dots are not the secret. The optional **Name** identifies the app in LibreChat; it does not rename the bot in Slack. Save the registration. Secrets are encrypted and cannot be read back. +3. Turn **Socket Mode off** in that Slack app before configuring HTTP callbacks. Expand the saved app in LibreChat and open the gear button, **Connection settings**. Configure all three copied URLs in that same Slack app: + + | LibreChat value | Slack setting | + | --- | --- | + | OAuth redirect URL | **OAuth & Permissions → Redirect URLs**; add the URL and save | + | Events URL | **Event Subscriptions → Enable Events** on; paste into **Request URL** and wait for **Verified** | + | Interactivity URL | **Interactivity & Shortcuts → Interactivity** on; paste into **Request URL** and save | + + Copy the exact URLs, including application base paths and organization/app suffixes. Saving the credentials first lets the gateway authenticate Slack's verification request before installation. If verification fails, check the matching signing secret, public gateway address, and running gateway before continuing. +4. Under **OAuth & Permissions → Bot Token Scopes**, add all seven scopes below. No user-token scopes are required: + + ```text + app_mentions:read + chat:write + reactions:read + reactions:write + files:write + im:write + im:history + ``` + +5. Under **Event Subscriptions → Subscribe to bot events**, add `app_mention`, `message.im`, `reaction_added`, `reaction_removed`, `app_uninstalled`, and `tokens_revoked`, then save. The last two require no additional OAuth scopes. The optional [native Agent sessions](#native-agent-ui-and-final-replies) setup additionally uses `agent_session_stopped`. +6. Under **App Home**, enable the **Messages tab** and allow users to send messages from it so direct messages can reach the bot. Leave token rotation and organization-wide installation disabled. Workspace approval policies still apply. + +These are manual Slack settings; LibreChat's organization app screen does not generate a manifest or create the Slack app for you. See Slack's [app setup](https://docs.slack.dev/app-management/quickstart-app-settings/) and [interactive request setup](https://docs.slack.dev/interactivity/handling-user-interaction/) for its dashboard controls. + +Use the pencil button, **Edit App**, to change its friendly name or credentials later. Leaving a secret blank preserves its saved value; clearing **Name** removes the custom LibreChat name. Changing the client ID requires entering the corresponding client secret again. The saved app ID cannot be edited: register another app for a different ID. After adding Slack scopes to an installed app, reconnect it through LibreChat and approve the updated grant; changing app settings alone does not upgrade existing tokens. A conflicted edit requires refreshing the registration before retrying. + +The organization flow does **not** ask you to paste a bot token or an app-level token. A bot token beginning `xoxb-` is used only by [manual connection activation](#set-up-a-manual-slack-app-connection); an app-level token beginning `xapp-` is used only by the manual Socket Mode gateway. Neither is the client secret, signing secret, or `CHANNEL_SERVICE_TOKEN`. + +### Connect and use a workspace + +1. Under the registered app, select **Add to workspace**. If Slack asks where to install, choose **A workspace** and the intended workspace, then approve installation. LibreChat shows a **Connecting to Slack…** dialog while this is pending. Use **Cancel** there to abandon the attempt, or **Retry** after a failure. On success, the dialog closes and the workspace appears as **Connected** below the app. Each app can have only one active workspace; reconnect that workspace to refresh its grant, or disconnect it before installing the app elsewhere. +2. An administrator opens the workspace's users button, **Manage access**, and adds the LibreChat users allowed to assign agents through that bot. The dialog is titled **Manage bot access**. Without explicit managers, administrators can still assign agents. These grants do not grant Agent sharing permission, Slack credentials, or installation-administration rights, and are not required just to talk to the bot. +3. Save an Agent you have permission to share. In the Agent builder's **Channels** section, select **Add Slack channel**, choose the **Slack bot**, then **Continue**. You need Remote Agents permission as well as permission to share the Agent. The picker offers only bots you can manage; installation administrators can also assign agents. +4. Choose **Default** for new direct messages and mentions without a channel-specific assignment, or choose **Channel Specific** and enter the **Slack channel ID**. Find the ID in Slack's channel details; it is not the channel name. Select **Assign agent**. If already assigned, **Replace default agent** or **Replace channel agent** opens a confirmation. The current Agent is named only when you can view it. Existing conversations keep their original Agent. +5. In Slack, invite the bot to your test channel, for example `/invite @YourBot`, then mention it with `@YourBot hello`. Installing the app alone does not add it to every channel. You can also send a direct message when the Messages tab is enabled. +6. Select **Link account** in the bot's private message. Sign into the intended LibreChat account, inspect the request, and accept it in the browser. The original Slack message changes to show the account name and **Confirm account**. Return to that message and confirm from the **same Slack account**. Once it shows **Account connected**, send a **fresh request** to the bot; the original request is not replayed automatically. If the confirmation does not appear, mention the bot again to recover the prompt. + +A successful first response completes setup. Each person repeats the personal linking step and runs with their own current Agent permissions, tool credentials, and approvals. Sharing a Slack thread does not lend another participant's MCP connection or authority. To verify tool access, ask for a task using a tool that the linked account is permitted to use, and complete any required approval as that account. + +#### If the bot does not respond + +| Symptom | Check | +| --- | --- | +| Events URL will not verify, or Slack callbacks return `401` | Gateway is running and publicly reachable over HTTPS; URL matches the saved organization/app; **Signing Secret** belongs to that same Slack app. Check clock synchronization against the signature window. Do not disable signature checks. | +| Gateway calls to LibreChat return `401` | API and gateway use the same private `CHANNEL_SERVICE_TOKEN`, and `LIBRECHAT_INTERNAL_URL` reaches the intended LibreChat instance. This is separate from Slack signature verification. | +| Mention produces no response | Check gateway readiness and logs, **Socket Mode off**, **Events on** with the subscriptions above, bot invited to the channel, and an Agent assigned to this bot/destination. Confirm the sender's personal link is active. | +| Confirmation, approval, or Stop buttons do nothing | **Interactivity** is on, its exact Request URL is saved, and the gateway is reachable. The Events URL does not substitute for the Interactivity URL. | +| Link says **Awaiting confirmation** | Return to the bot's private message and select **Confirm account** from the same Slack account. If the message did not update, mention the bot again to recover the prompt. After confirmation, send a new request. | +| Installation reports missing permissions after scopes changed | Add all required bot scopes, then reconnect through LibreChat and approve the updated grant. Existing installations do not receive new scopes automatically. | + +Use the same Compose project and overlays from operator setup when checking `ps` and `logs --tail=100 api channel-gateway`. A healthy application alone does not prove the separate gateway is ready. See [gateway readiness checks](#docker-compose). + +### Disconnect and remove + +- Administrators revoke bot management through the workspace’s **Manage access** users button. Removed managers cannot create or change assignments, but can still remove assignments or delete their own Agent connection. Existing assignments and conversations keep running; management access is separate from permission to talk to the bot. Disconnecting a workspace clears its manager grants; a fresh installation requires new grants. Reauthorizing an active installation preserves managers. + +- **Agent Builder → Channels → Manage connection** lists current assignments. When another destination is available, select **Add assignment** to configure it, or **Remove** beside an assignment and confirm **Remove assignment** to stop using it for new conversations. Select **Delete connection** and confirm to remove the connection and all its assignments atomically; existing threads retain their original identity and cannot silently move to another Agent. Disabling a connection stops execution without replacing its routing choices. To resume, select **Activate channel** and confirm the retained assignment. Each sender must link and confirm their account again after reactivation. +- **Settings → Account → Your channel account links** shows personal links once any exist. Status pills distinguish **In progress**, **Awaiting confirmation**, **Active**, **Revoked**, **Expired**, and **Unavailable**. Select **Revoke link** and confirm to withdraw your own consent, including an unavailable link. Completed actions are not undone. +- **Settings → Account → Slack → Manage**, then the workspace’s **Disconnect workspace** icon retires the installation's access in LibreChat. It does not uninstall the app in Slack. A later installation gets a fresh identity, so old connections do not resume automatically. Reconnect an active installation to update its grant; disconnect it first if you want to retire existing connections. +- After disconnecting an app’s active workspace, use the app’s trash button, **Remove app**, and confirm to delete its saved app credentials. This does not delete the Slack app itself. +- Removing the configuring person does not delete the organization's installation. Their personal links and credentials are cleaned up, and their Agent connections are retired through the normal channel cleanup. +- To stop the optional service, run the same Compose file list and project with `stop channel-gateway`, or set `channelGateway.enabled: false` and remove the shared opt-in from Helm/API configuration. Ordinary LibreChat can continue. Database topology is independent: do not remove an external replica configuration or convert existing data merely to stop the gateway. + +## Set up a manual Slack app connection + +1. Save the Agent, then select **Channels → Add Slack channel** in the Agent builder. Choose per-user or owner-only mode and continue. You need permission to share the Agent. +2. Copy the read-only Slack app manifest into Slack's app setup and install the app into your workspace. Make any app changes in Slack's app settings. +3. Under **Add Credentials**, enter the matching Slack app ID and bot token. For webhooks, also enter its signing secret. Activate the connection. LibreChat verifies the bot identity and stores credentials encrypted; normal setup responses do not return those secrets. +4. Copy the connection ID from the connection details. Configure and start the gateway as described below. It must be able to reach the active connection before startup can complete. +5. Invite the bot to a Slack channel and mention it. Each sender completes their own account-link flow before their request can run. + +Browser setup routes live under `/api/agents/{agentId}/channels`: list/create, read `/{channelId}/manifest`, update `/{channelId}`, and delete `/{channelId}`. These use authenticated Agent authorization, not the management API machine-token contract. The create body is `{ "type": "slack", "credentialMode": "per_user" }` (or `shared`). Activation updates use a `slack` object with `appId`, `botToken`, and optional `signingSecret`. To disable a channel, update its status to `disabled`. + +## Run the Slack gateway + +The gateway is a separate Node.js 24+ process, not an in-process LibreChat plugin. It uses Slack's official Node SDKs and calls LibreChat over HTTP; it does not connect directly to MongoDB or require a general-purpose Agent API key. Manual single-connection mode binds one process to one tenant and connection. Shared mode omits those IDs and serves all installed workspaces with a bounded worker pool. + +From a LibreChat source checkout containing `packages/channel-gateway`: + +```sh +npm ci +npm run build:packages +npm run build --workspace @librechat/channel-gateway +node --env-file=/secure/channel-gateway.env packages/channel-gateway/dist/main.cjs +``` + +`npm run backend:dev` starts only LibreChat; it does not start the gateway. Build the application using LibreChat's normal development setup and run the gateway separately. If you reuse the application's `.env`, set the gateway port explicitly: `PORT=8080 node --env-file=.env packages/channel-gateway/dist/main.cjs`. This avoids binding the gateway to the application's port. + +Supply the environment file through your normal secret-management process. Do not commit it. A Socket Mode configuration is: + +```dotenv filename="channel-gateway.env" +LIBRECHAT_INTERNAL_URL=http://127.0.0.1:3080 +CHANNEL_SERVICE_TOKEN= +CHANNEL_TENANT_ID=_ +CHANNEL_ID= +SLACK_TRANSPORT=socket_mode +SLACK_APP_TOKEN= +``` + +`LIBRECHAT_INTERNAL_URL` must be reachable from the gateway process. For example, `127.0.0.1` refers to the gateway's own container when running inside a container. Use the exact tenant ID for a multi-tenant deployment; use `_` only for a single-tenant deployment without a tenant ID. The gateway obtains the bot token and signing secret from LibreChat's authenticated connection bundle, so those values do not need separate gateway environment variables. + +For Socket Mode, enable Socket Mode for the same Slack app and create an app-level token with `connections:write`. Keep that token only in the gateway's secret configuration. No public callback URL is needed, but the gateway needs outbound access to Slack and LibreChat. + +For webhooks, set `SLACK_TRANSPORT=webhook` instead and omit `SLACK_APP_TOKEN`. Put the gateway behind HTTPS and set these Slack request URLs, replacing the placeholders with the configured, URL-encoded IDs: + +```text +Events: https://gateway.example.com/slack/events// +Interactivity: https://gateway.example.com/slack/interactions// +``` + +The webhook receiver verifies Slack signatures using the connection's signing secret. Keep LibreChat's channel service routes private to trusted integrations. Expose only the required Slack callback paths publicly; keep health and metrics endpoints on an internal network. + +### Docker Compose + +The optional `docker-compose.channels.yml` overlay pulls release-matched LibreChat and gateway images. Set `LIBRECHAT_IMAGE_TAG` in `.env` to the release tag you are installing (including `-rcN` for a release candidate); both images use that same tag. If omitted, both use the latest stable release. Images are published for Linux AMD64 and ARM64 when the corresponding LibreChat release is published. A tag predating Agent Channels does not contain a gateway image. The separate `docker-compose.mongo-replica.yml` overlay initializes a single local MongoDB replica member and waits for a writable primary before the application starts. The gateway waits for application health, then authenticates its channel binding and selected Slack transport. Neither service is added to the default stack automatically. + +For a **fresh local database**, run these commands from the LibreChat repository root: + +1. Prepare `.env` and `librechat.yaml` using the normal LibreChat setup and the channel policy above. Set `LIBRECHAT_IMAGE_TAG` to the published LibreChat release containing Channels. Generate a private service token with `openssl rand -hex 32` and set `CHANNEL_SERVICE_TOKEN` in `.env`. Keep the same Compose files and project name on subsequent commands. +2. Pull and start the application and its dependencies: + + ```sh + docker compose -f docker-compose.yml -f docker-compose.channels.yml \ + -f docker-compose.mongo-replica.yml up -d --pull always api meilisearch + ``` + +3. Sign in, save an Agent, and [set up its Slack channel](#set-up-a-manual-slack-app-connection). Enter the bot token in the Agent builder. Set `CHANNEL_ID` in `.env` to the resulting connection ID. `CHANNEL_TENANT_ID` defaults to `_` for an ordinary OSS installation; supply the actual tenant ID for a tenant-scoped installation. For Socket Mode, also set `SLACK_APP_TOKEN` to the matching app-level token with `connections:write`. +4. Start the gateway: + + ```sh + docker compose -f docker-compose.yml -f docker-compose.channels.yml \ + -f docker-compose.mongo-replica.yml --profile channels up -d --pull always channel-gateway + ``` + +5. Check readiness and startup errors: + + ```sh + docker compose -f docker-compose.yml -f docker-compose.channels.yml \ + -f docker-compose.mongo-replica.yml --profile channels ps -a + docker compose -f docker-compose.yml -f docker-compose.channels.yml \ + -f docker-compose.mongo-replica.yml --profile channels logs --tail=100 api mongodb-init channel-gateway + ``` + +`mongodb-init` exiting with status 0 is expected. The application and gateway should be running and healthy. A successful Slack credential activation does not prove the gateway is connected: check health and perform a real mention. Missing service/app bindings prevent gateway startup. + +The overlay points `LIBRECHAT_INTERNAL_URL` at `http://api:${PORT:-3080}` on the Compose network. The gateway listens on container port `8080`, with no published host ports. Socket Mode needs no inbound port; webhooks need an HTTPS reverse proxy forwarding the required callback paths to `channel-gateway:8080`. The gateway runs as a non-root user with a read-only filesystem and dropped capabilities. + +For an existing external transaction-capable database, omit the local MongoDB replica overlay and provide the application connection through your normal Compose override. The base Compose file sets the API service's `MONGO_URI`, so changing only `.env` does not replace that service environment entry. Never run the local initializer against an external database. + +A separate gateway environment file can be supplied through `--env-file .env --env-file /secure/channel-gateway.env`. Keep `CHANNEL_SERVICE_TOKEN` identical in LibreChat's own `.env`; Compose substitution does not update the file mounted into the application. + +For development or an unreleased checkout, add `-f docker-compose.channels-build.yml` immediately after `-f docker-compose.channels.yml` on **every** Compose command. This explicitly builds both services from the checkout instead of using release images. Use `up -d --build` instead of `up -d --pull always` on that path. The normal release path needs no local Node.js installation or image build. + +To build only the gateway image, use the repository root as its build context: + +```sh +docker build -f packages/channel-gateway/Dockerfile \ + -t librechat-channel-gateway:local . +``` + +#### Existing local data + +Back up the database, stop LibreChat and every gateway/worker that writes to it, then stop the database before applying the replica overlay. Retain the existing `data-node` directory; do not delete it or remove its volume. Start with that same data directory and the overlays above. Initialization preserves existing documents and only initializes an unconfigured local `rs0` member at `mongodb:27017`. It refuses a different replica configuration rather than silently reconfiguring it. Verify existing users/history before enabling the gateway, and retain the overlay on future restarts. + +A single local replica member enables transactions but does not provide high availability. The overlay follows the base development Compose authentication/network settings; it is not a production replica-set recipe. For authenticated, multi-member or production deployments, use the [MongoDB conversion procedure](https://www.mongodb.com/docs/manual/tutorial/convert-standalone-to-replica-set/) and your deployment's backup/security practices. + +### Gateway settings + +| Environment variable | Default | Purpose | +| --- | --- | --- | +| `PORT` | `8080` | Gateway HTTP port | +| `LISTEN_HOST` | `0.0.0.0` | Listen address; use loopback for a host-only local process | +| `LIBRECHAT_INTERNAL_URL` | `http://localhost:3080` | LibreChat service origin | +| `CHANNEL_SERVICE_TOKEN` | Required | Shared server-side service-boundary token | +| `CHANNEL_TENANT_ID` | Required | Exact connection tenant; `_` for single-tenant deployments | +| `CHANNEL_ID` | Required | Connection ID, not the Slack channel ID or Agent ID | +| `SLACK_TRANSPORT` | Server policy | Optional assertion: `webhook` or `socket_mode`; must match the server policy | +| `SLACK_APP_TOKEN` | Required for Socket Mode | App-level token for the same Slack app | +| `APP_TITLE` | `LibreChat` | Application name in browser-review button labels, such as “Open in LibreChat” | +| `API_CALL_TIMEOUT_MS` | `10000` | Timeout for individual service and Slack API calls | +| `AGENT_INVOKE_TIMEOUT_MS` | `120000` | Timeout for initial background Response submission, not the full Agent run | + +`SLACK_API_BASE_URL` defaults to `https://slack.com/api/`; leave it unchanged outside controlled tests. Ingress mode, native sessions, lifecycle reactions, signature tolerance and reconnect pacing come from `endpoints.agents.channels` in `librechat.yaml`, not process feature flags. Actor-bound durable execution cannot be disabled. + +The process exposes `/livez` for HTTP-process liveness, `/readyz` and `/healthz` for actual startup/transport readiness, and `/metrics` for Prometheus metrics. An observed ingress-mode or Socket app-binding mismatch makes the gateway unready. Restart gateway replicas after changing ingress mode or Socket app identity. A successful connection activation confirms Slack credentials, not that a gateway is running or connected. Check gateway health and perform a real mention before treating setup as complete. + +### Native Agent UI and final replies + +By default, a stable working message provides Stop and, when needed, Review controls. Answers are posted after generation, rather than repeatedly replacing a growing reply. Formatting includes code, tables, links, file attachments, and qualified cost/time details; unavailable costs are not estimated. + +To opt into Slack's native Agent sessions, enable Agent mode in the Slack app, add the `assistant:write` bot scope and `agent_session_stopped` event subscription, and reinstall the app. These are additional to the basic bot configuration above. Then set `endpoints.agents.channels.nativeSessions: true` in `librechat.yaml`. Without the required permissions or Stop subscription, the gateway falls back to durable progress controls. See [Slack Agent sessions](https://docs.slack.dev/ai/agent-sessions/) for app configuration. + +Native sessions show processing or waiting-for-input status and return to active when the canonical run finishes. Completed answers can include Helpful/Not helpful controls. Optional lifecycle reactions use `endpoints.agents.channels.lifecycleReactions: true`, are separate from native sessions and require the reaction permissions already listed in the basic bot scopes. Neither an acknowledgement nor a reaction proves a tool completed; Stop requests cancellation of the sender's authorized work, not another participant's run. + +## Kubernetes and Helm + +The LibreChat Helm chart supports a shared workspace gateway or a manually bound gateway. For manual single-connection setup, first select a published LibreChat release containing Channels through `image.tag` and upgrade the application with the gateway disabled. Configure the channel/background policy above in your existing `librechat.yaml`, then create and activate the connection in the Agent builder. Enable the gateway after obtaining its connection ID. + +Enabling the gateway requires an explicit `image.tag` selecting a published LibreChat release containing Channels; the chart refuses to silently use its older application-version default. The gateway image tag defaults to that same selected release. Earlier releases have no gateway image. A custom `channelGateway.image.tag` is available for deliberate image overrides. + +### Shared workspace deployment + +Select a release containing organization workspaces and provide an existing Secret with `CHANNEL_SERVICE_TOKEN`. The service token is shared with LibreChat. No Slack app credentials belong in this Secret: organization admins register their apps in the UI after deployment. Existing explicit API environment entries take precedence over the generated service-token reference. + +The example below selects a replica set for a **fresh bundled MongoDB**. For an existing standalone database, plan and complete its data migration before changing topology; a Helm value does not migrate existing data. For an external transaction-capable database, supply its connection through your normal application configuration and disable the unused bundled database with `mongodb.enabled: false`. Shared mode refuses to render when LibreChat would use a bundled standalone MongoDB; existing external connection overrides remain supported. + +```yaml filename="shared-workspace-values.yaml" +image: + tag: +mongodb: + architecture: replicaset +channelGateway: + enabled: true + mode: shared + replicaCount: 2 + existingSecretName: gateway-service-credentials + ingress: + enabled: true + host: slack.example.com + tls: + - hosts: [slack.example.com] + secretName: slack-gateway-tls +``` + +Set `DOMAIN_SERVER` and `DOMAIN_CLIENT` through the application's normal environment settings. Use a transaction-capable database; the chart does not convert MongoDB topology. Shared mode supplies the app enablement flag and derives `SLACK_GATEWAY_URL` from its ingress host unless explicitly configured in `librechat.configEnv`. It omits connection IDs, tenant IDs and the Socket Mode token. Shared replicas use rolling updates and the existing durable claims; `mode: single` requires one replica and uses replacement updates. After deployment, organization admins register their apps and configure the displayed Slack URLs through Account settings, then use Agent Builder; no per-workspace Helm values or pods are required. + +### Manual connection deployment + +```yaml filename="channel-values.yaml" +image: + tag: +channelGateway: + enabled: true + channelId: + tenantId: "_" + # Defaults to global.librechat.existingSecretName. + existingSecretName: librechat-credentials-env +``` + +For branding, `channelGateway.appTitle` defaults to `librechat.configEnv.APP_TITLE`, then `LibreChat`. Set it explicitly when the API obtains `APP_TITLE` through a Secret or `global.librechat.env`, or choose a shorter gateway title if the application name is not compatible. Gateway titles must be nonempty after trimming, at most 40 UTF-16 characters, and contain no `<`, `>`, `|` or control characters. Helm rejects incompatible inherited titles before deployment; the application can keep its existing longer name. + +For manual Socket Mode, the existing Secret must contain `CHANNEL_SERVICE_TOKEN` matching LibreChat's server configuration and `SLACK_APP_TOKEN` for the matching app. By default, the application's existing Secret supplies the same service token to LibreChat. The gateway references only these individual keys, not all provider/database credentials. If you choose a separate gateway Secret, keep its service token identical to the API's. Rotate through your normal secret-management process and restart the API and gateway after rotation; changing an existing Secret does not automatically restart pods. + +The gateway derives the API URL from the chart's internal Service and port. Override `channelGateway.internalUrl` for a deliberately different service boundary. Configure gateway resources and annotations through `channelGateway`. Pod identity, `enableServiceLinks`, DNS, host aliases, image pull secrets, node selectors, tolerations and affinity follow the chart's shared platform settings. The configured ServiceAccount is reused while the gateway keeps its Kubernetes API token automount disabled. Manual single mode installs one gateway with replacement updates, so an upgrade may briefly interrupt Slack connectivity; it does not create another channel or Slack app. + +For webhook transport, set the server policy to `webhook`, configure its public HTTPS gateway URL and clear `channelGateway.slackAppTokenKey`. Enable the dedicated gateway Ingress using your installed ingress controller and a trusted TLS certificate: + +```yaml filename="channel-values.yaml" +channelGateway: + slackAppTokenKey: "" + ingress: + enabled: true + host: gateway.example.com + # Empty inherits ingress.className; set this for a different controller. + className: nginx + annotations: {} + tls: + - hosts: [gateway.example.com] + secretName: gateway-tls +``` + +Combine these settings with the enabled gateway and explicit application release above. Set the server's `gatewayUrl` to `https://gateway.example.com`, with no path prefix. The new Ingress routes only `/slack/events` and `/slack/interactions` to the gateway Service on port 8080; its health and metrics routes remain private. It can share the application's hostname: the more specific Slack paths route to the gateway while the existing application Ingress keeps its own backend. Gateway annotations are separate so application rewrite rules cannot silently change callback paths. The gateway Ingress requires Kubernetes 1.19+. + +The chart does not install an ingress controller or issue a TLS certificate. Configure TLS through the example Secret, your normal certificate controller/annotations, or an existing HTTPS termination layer. Slack needs a publicly trusted HTTPS endpoint. If you manage a separate external Ingress or need a custom base-path rewrite, leave `channelGateway.ingress.enabled: false` and route the callback paths to the rendered gateway Service yourself; do not add them as paths on the main application Ingress, whose backend is LibreChat. Socket Mode needs no public ingress. + +By default, the gateway runs without an automatically mounted Kubernetes API token, as a non-root user with a read-only filesystem and dropped capabilities. The chart's global pod/container security contexts are honored for platform-specific overrides. Gateway version labels follow its selected image tag. `/livez` drives liveness and `/readyz` drives readiness. Startup before the API or channel is available fails closed; Kubernetes retries the process, and it becomes ready after validating the shared app policy or its bound active connection and transport. + +**The chart does not configure or convert MongoDB topology.** Supply a transaction-capable database before enabling Channels. The default standalone database cannot run this feature. See the [Helm database configuration](/docs/local/helm_chart) for external database credentials. Normal LibreChat remains usable without enabling Channels. + +After applying the values through your normal Helm upgrade, check the gateway Deployment, pod events, logs and readiness. Missing Secret keys block pod startup; incorrect credentials or channel bindings prevent readiness. Finish with a real Slack mention and account linking rather than treating a successful Helm release as proof of integration. + +## Rich Agent messages + +An Agent can return semantic presentation data in its final answer. The gateway validates the document and converts it deterministically to Slack Block Kit; it does not accept arbitrary Slack JSON, callback IDs, approval controls, file identifiers or modal definitions. Ordinary Markdown remains unchanged. This works in both ordinary threads and native Agent sessions, without a new OAuth scope or a configuration migration. + +To enable this behavior for an Agent, add the following to its instructions using the existing Agent editor (or include it in a request when testing): + +> When a chart, table, image or card would help, write a short readable answer followed by exactly one final, top-level fenced code block named `librechat-presentation`. Its JSON must be `{ "version": 1, "blocks": [...] }`. Use only the semantic components described below; never emit raw Slack blocks or interactive callback IDs. Use ordinary Markdown for other answers. Use public HTTPS URLs for images and links. The gateway generates complete text fallbacks from the data, so do not repeat every value in the introductory answer. + +For example, ask the Agent to compare the following measured results and show both a chart and a table. Its final answer can be: + +````text +Latency fell from 20 ms to 17 ms. + +```librechat-presentation +{ + "version": 1, + "blocks": [ + { + "type": "chart", + "title": "Latency", + "chart": { + "type": "line", + "axis_config": { "categories": ["Mon", "Tue"], "y_label": "ms" }, + "series": [{ "name": "p95", "data": [{ "label": "Mon", "value": 20 }, { "label": "Tue", "value": 17 }] }] + } + }, + { "type": "table", "title": "Measurements", "columns": ["Day", "p95 (ms)"], "rows": [["Mon", 20], ["Tue", 17]] } + ] +} +``` +```` + +| Semantic component | Fields and constraints | +| ----------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `text`, `header`, `context`, `code` | `text` string; maximum 3000, 150, 2000 and 3000 Unicode characters respectively. Text components use literal text, so mentions cannot grant interaction authority. Use ordinary Markdown outside the document for formatted prose. | +| `divider` | No additional fields. | +| `image` | `url`, `alt`, optional `title` (150 characters). HTTPS URLs only; alt text is required (2000 characters). The gateway never fetches remote media. | +| `card` | Required `title` (150), optional `subtitle` (150), `body` (200), `image: {url, alt}`, and up to three `links: [{label, url}]`. Labels are at most 75 characters. Links navigate only; they cannot run an Agent action. | +| `carousel` | `cards`: 1–10 cards. | +| `table` | `title` (150), `columns`: 1–20 labels (150 each), `rows`: 1–200 rectangular rows of strings or finite numbers. Maximum 20,000 characters across cells including headers. Numeric cells retain numeric sorting. | +| `chart` | `title` (50), `chart` as described below. | +| `container` | `title` (150), `blocks`: 1–10 text, header, context, code, divider or image components. | +| `layout` | `title` (150), `rows`: 1–5 arrays of 1–3 components, including containers. Charts, tables and carousels each occupy their own row. Layouts cannot nest. The gateway makes the title visible and generates explicit compatibility fallback blocks. | + +Charts support `line`, `bar` and `area` with `axis_config: {categories, x_label?, y_label?}` and `series: [{name, data: [{label, value}]}]`. Categories have 1–20 distinct labels; each series must have exactly one point per category. Series names are distinct; at most 12 series are allowed. Names and labels are at most 20 characters, axis titles 50. Values must be finite; negative values are supported. Pie charts instead use `chart: {type: "pie", segments: [{label, value}]}` with 1–12 distinct labels and positive finite values. + +A document has at most 40 components and the final answer must fit the 128,000-byte presentation parsing boundary. Malformed, incomplete, quoted, ambiguous or unrecognized documents stay on the ordinary text path. The gateway isolates components into messages, counts nested blocks, enforces two charts and 20,000 table characters per message, and preserves every value and link in generated text. Components which exceed Slack's nested block or serialized payload limits become text. The server's existing `maxDeliveryParts` policy still applies, with an explicit shortening notice when needed. + +The gateway freezes versioned blocks and text together before posting. Retries replay that plan. A definitive Slack `invalid_blocks` or `invalid_attachments` rejection permits a text-only retry; ambiguous network outcomes retain the existing reconciliation behavior. Clients that cannot render layouts receive the generated stable fallback blocks. + +This is rich **message** support. Existing app-owned Stop, approval, question and feedback workflows remain responsible for interactions. New input forms, App Home, modals, Work Objects, plan/task execution controls and embedded video are not exposed to Agent output. Authorized file attachments continue through the existing artifact delivery path. Slack may limit newer blocks by client or app availability; automated tests cover SDK payloads and rejection fallback, while live rendering requires a configured Slack workspace. + +## Identity and permissions + +### Shared versus per-user credentials + +`per_user` binds each linked Slack actor to that actor's existing LibreChat account. Current tenant membership, account state, role capabilities, Agent permissions, and tool/provider credentials are evaluated for that user. + +`shared` does **not** make the Agent public to every Slack participant. Linking is restricted to the channel creator's LibreChat account; execution uses that explicitly linked principal's authority. Do not treat the activator's API key, a Slack display name, an email assertion, or another participant's thread history as consent for a new actor. + +Each actor/link revision has its own execution lane. Sharing a Slack thread does not share a LibreChat conversation or expose another actor's files, OAuth state, approvals, usage, or trace session. A different actor cannot inherit the first participant's pending question or approve its tool call. Mode/revision changes, unlinking, account removal, Agent access revocation, and key revocation invalidate old authority rather than silently remapping it. + +### Account linking + +On the first mention, the gateway sends the actor a private linking prompt. Open its trusted LibreChat URL, sign in, preview the request, and accept it. The same private Slack message updates to show the account name and **Confirm account**. Confirm it from the same Slack account, then send a fresh request once it shows **Account connected**. If the prompt has expired or does not update, mention the bot again to recover it. The gateway does not replay the original request after linking, and browser acceptance alone does not activate the link. + +Manage and revoke your links in Account settings. A link can become unavailable when the channel or account is no longer eligible; you can still withdraw that consent. Restoring eligibility does not reactivate a link you explicitly revoked. + +Browser endpoints under `/api/channels/links` support preview, acceptance, listing, and revocation. Mutations require the normal authenticated browser and same-origin checks. Ticket lifetime defaults to 10 minutes; accepted links default to 30 days. Never post a bearer credential or provider OAuth URL into Slack. + +## Delivery recovery + +The gateway preserves each frozen delivery operation and its receipt. A required operation whose provider result is unknown holds its actor lane: retrying blindly could post the same answer twice. Stopping a worker or requesting cancellation does not prove whether Slack accepted an earlier request. + +### Reconcile a verified provider result + +1. Pause gateway processing for the affected channel while investigating. Keep the channel enabled and preserve its actor link and source authority; disabling these is not a recovery bypass. +2. Inspect the frozen run plan and retained receipt. Verify in the provider that the exact operation reached the intended channel or thread. Record its message key or file ID, matching the retained attempt and any upload checkpoint. If the result cannot be verified, leave the receipt unknown and the lane held. +3. After the old lease expires, use `POST /api/channels/service/:channelId/runs/claim` with `{ "owner": "" }`, normal private service authentication and tenant binding. Verify that the returned run ID is the run being investigated; claims select eligible work and do not take a target run ID. Use the returned current fence, renewing it through `POST /renew` as needed. An expired or replaced worker's fence cannot reconcile the run. +4. Submit the confirmed receipt through the private channel API. These endpoints confirm existing attempts; they do not send messages, replace the frozen plan, reset attempts or accept a retryable outcome. + +All paths below are relative to `/api/channels/service/:channelId/runs/:runId`. Send the channel service bearer token and the configured tenant header, using the same internal service URL as the gateway. Preserve the complete fence returned by the claim. + +For a delivery operation, `POST /delivery/reconcile`: + +```json +{ + "fence": { "runId": "", "owner": "", "epoch": 1 }, + "receipt": { + "operationId": "", + "outcome": "confirmed", + "attempts": 1, + "externalMessageKey": ":" + } +} +``` + +Replace every illustrative value with the actual current fence and retained attempt count; do not assume the example epoch is current. Supply `externalFileId` for a verified file operation as required by its frozen operation and checkpoint. Existing provider identities are immutable; reconciliation cannot replace them with a different message or file. + +For a progress update, `POST /progress/reconcile` with the current `fence`, the retained progress `revision`, and a `receipt` containing `outcome: "confirmed"`, the retained `attempts` and verified `externalMessageKey`. The message identity remains fixed across progress revisions. + +Once every required delivery operation is confirmed, `POST /commit` with the current `fence` to complete delivery and release the actor lane. Resume normal gateway processing afterward. If any required result remains uncertain, keep the run held rather than resending or recreating it. Optional exhausted effects follow the gateway's bounded recovery policy and do not justify replaying a required answer. + +## Gateway protocol boundary + +Service requests use `Authorization: Bearer ` and the exact tenant/channel context. For routes without a tenant path parameter, send the channel's exact tenant in `x-tenant-id`; do not choose a tenant from untrusted message text. The service token permits this integration boundary, not arbitrary user impersonation. Never put it in client-side code, Slack messages, tracing payloads, or public logs. The credential bundle endpoint returns secrets to the trusted gateway and must not be exposed as a public discovery API. + +| Stage | Route under `/api/channels` | Responsibility | +| --- | --- | --- | +| Configuration | `GET /service/slack/{tenantId}/{channelId}` | Read the active channel and encrypted-at-rest credentials through an authenticated service boundary | +| Identity | `/service/{channelId}/identity/{externalUserId}` and its ticket/confirm operations | Resolve explicit actor linking; never infer a principal from thread history | +| Admission | `POST /service/{channelId}/runs` | Admit an idempotent event with verified team/app/actor identity | +| Ownership | `POST /service/{channelId}/runs/claim`, then `/{runId}/renew` or `/{runId}/yield` | Claim, renew, or safely yield the exact polling lease | +| Execution preparation | `POST /service/{channelId}/runs/{runId}/response/prepare` | Obtain the bound execution credential, request, and stable request key | +| Response binding | `POST /service/{channelId}/runs/{runId}/response/bind` | Bind the returned Responses identity to the admitted run | +| Progress and result | `/{runId}/advance`, `/{runId}/progress/*`, `/{runId}/plan`, `/{runId}/delivery/*`, `/{runId}/commit` | Project canonical state and record fenced delivery effects | +| Review projection | `POST /service/{channelId}/runs/{runId}/review` | Reauthorize the original actor; obtain the browser fallback and, for the exact external channel, the current private question/tool-review payload | +| Review decision | `POST /service/{channelId}/runs/{runId}/review/decision` | Submit the current actor's decision to the same canonical Response, using its immutable decision receipt and resume preflight | +| Artifacts | `POST /service/{channelId}/runs/{runId}/artifacts/{artifactId}` | Read only an artifact bound to the authorized run | +| Stop | `POST /service/{channelId}/runs/stop-thread` or `/{runId}/cancel` | Cancel only the authorized actor lane/run | +| Feedback | `POST /service/{channelId}/reactions` | Apply authenticated, deduplicated reaction events to confirmed delivered replies | + +Run-relative paths in the table are beneath `/service/{channelId}/runs`. Lease mutations carry the exact server-issued `{ runId, owner, epoch }` fence. Treat stale leases, changed authority, and ambiguous delivery outcomes as protocol states to reconcile, not permission to mint a new run or blindly resend a Slack message. + +A gateway can yield a Responses-bound polling turn only after its progress writes and renewal loop have drained. The backend invalidates that polling fence and enforces a one-second reclaim cooldown; the next claim increments the epoch. This does not release the actor lane, cancel the provider, change the Response, or erase delivery evidence. Inflight or unknown remote writes cannot yield. If a host does not support yield or the request is uncertain, retain ordinary lease-expiry recovery instead of starting a new execution. + +Execution preparation returns an `apiKey`, `requestKey`, optional retained `responseId`, and a Responses `request` with the Agent ID, admitted input, `background: true`, `store: true`, and any authorized continuation. Submit that request to `/api/agents/v1/responses` using the bound key and `Idempotency-Key: `, then bind the returned response ID. Do not substitute a creator's unrestricted key, change the actor/Agent, or send the input to a channel bookkeeping route to execute it. + +The gateway owns Slack event verification, retry/deduplication, native message formatting, rate-limit handling, and delivery receipt reconciliation. Preserve exact event identifiers and timestamps. For feedback events, `eventTimestampUs` is an integer timestamp in microseconds; do not round it to milliseconds. Report confirmed, failed, or ambiguous side effects accurately before advancing delivery state. + +## Review, files, feedback, and privacy + +The public run snapshot contains only an opaque action descriptor. A review request with the original verified actor, current channel revision, and exact `externalChannelId` can obtain minimal private question/tool-approval details. The backend derives the owner and Response from the run and checks the current actor link and source grant; it does not accept an arbitrary user ID. The gateway must show these details only to that actor, not publicly in a shared thread. Slack can use ephemeral choice buttons and private modals. Native tool approval is for the individual call only; unsupported/richer forms retain the browser fallback. + +Decision requests include that same actor, revision, external channel, and the existing Responses `decision` body. Backend decision receipts, expiry/conflict checks, full authorization preflight, and same-Response resume remain authoritative. A Slack click alone is not permission to resume work. Provider OAuth URLs remain exclusively in the authenticated browser review flow. See the [background Responses lifecycle](/docs/features/background_responses#review-a-required-action). + +The gateway uses private Slack controls for supported questions and individual tool approvals/declines. Tool names and argument previews, when supplied by the authorized review projection, remain private to that actor. Successful submission closes the modal and sends a private acknowledgement; it does not force a separate browser receipt page or claim the tool finished. Provider sign-in and unsupported review forms still open the authenticated browser flow. The Agent's existing approval policy determines whether a tool needs approval; connecting Slack does not make every tool manual or bypass that policy. + +A saved decision whose queue publication failed remains retryable with the exact immutable decision; it cannot be replaced by a different answer. Keep Stop available while work remains active and update public progress only through the fenced delivery protocol. + +Artifacts are scoped to the authorized run and actor. User-submitted rows do not count as server-generated provenance for artifacts or trace links. Limits default to five files and 10 MiB per file. One `artifactReadTimeoutMs` budget (30 seconds by default) covers catalog queries, storage reads and final authorization rather than restarting for every file. Limits resolution is separately capped at 60 seconds, and its elapsed time counts against the selected budget once available. An expired budget omits artifacts while retaining the answer for authorized text delivery. + +New plain-text answer files require more than `artifactAnswerFileThresholdBytes` UTF-8 bytes (6,000 by default); fenced answers also qualify. This threshold controls new projections, while saved descriptors retain their content identity and still require current authorization, retention and maximum-size checks. Do not replace an unavailable artifact with another actor's file or an unauthenticated internal download URL. Usage and cost summaries identify incomplete or unavailable coverage instead of presenting a partial subtotal as the complete thread total. + +Reaction feedback maps only to confirmed delivered assistant messages. It does not overwrite the browser's feedback field. Export is constrained by recorded sampling/destination provenance and current export policy. Temporary-chat provenance is retained; an old mapping without that provenance is not treated as a permanent chat. A policy change can suppress export even when the original trace was sampled. + +## Operational limits + +Disabling a channel or changing its credential mode retires old authority; it does not undo external tool effects. Stop can remain pending until the exact provider is drained and canonical cancellation/cleanup completes. A gateway must poll/reconcile canonical state rather than interpreting an acknowledgement as terminal completion. + +With the process-local job store, restarting the API while a tool approval is pending makes the review unavailable. The request stays held for reconciliation and a stale approval does not execute the tool. Durable checkpoint storage alone does not recreate the missing live execution state. Completed history and subsequent thread continuation survive restart. + +The gateway package is built from the LibreChat source tree; it is not published as an npm package. This integration does not promise arbitrary chat-platform support or restart-survival of an in-memory OAuth waiter. A restart can require a new browser sign-in; uncertain continuation must not be reported as completed or retried as new work. diff --git a/content/docs/features/agents_api.mdx b/content/docs/features/agents_api.mdx index 5289b5cfc..3eaef7297 100644 --- a/content/docs/features/agents_api.mdx +++ b/content/docs/features/agents_api.mdx @@ -16,16 +16,12 @@ There are three parts: Endpoints, request formats, and responses may still change between releases. +For opt-in asynchronous execution, retained human actions, owner review, and Stop, see [Background Agent Responses](/docs/features/background_responses). [Agent Channels](/docs/features/agent_channels) use that same Responses execution path while managing external identity and delivery separately. + ## Get an API key Open **Settings**, go to **Data & Privacy**, and find **Agent API Keys** under **API keys**. Click **Manage**, then **Create API Key**, give it a name, pick an expiration (7 days up to 1 year, or never), and copy the key right away: it is shown only once. - - A key acts as you. Anything you can reach through the API, anyone holding the key can reach too, so delete keys you no longer use. Deleting a key cuts off applications using it immediately. If you do not see the section, your role has not been given access to the Agents API. diff --git a/content/docs/features/background_responses.mdx b/content/docs/features/background_responses.mdx new file mode 100644 index 000000000..f7306615c --- /dev/null +++ b/content/docs/features/background_responses.mdx @@ -0,0 +1,106 @@ +--- +title: Background Agent Responses +description: Run durable Agent Responses, review pending actions, and stop an owned execution +icon: Workflow +--- + + +Background execution is a beta feature and is disabled by default. Use a LibreChat version that supports background Responses before enabling it. + + +Background Responses use the existing [Agents API](/docs/features/agents_api), not a separate model execution engine. A request returns a stored response identity while a worker executes the Agent. Clients poll that identity for output, required actions, and the canonical terminal result. + +## Enable background execution + +Merge this block into your existing configuration: + +```yaml filename="endpoints / agents" +endpoints: + agents: + remoteApi: + background: + enabled: true + acknowledgementTimeoutMs: 10000 +``` + +`enabled` defaults to `false`. `acknowledgementTimeoutMs` defaults to `10000` and accepts integers from `100` through `60000`; it bounds the worker admission acknowledgement, not total model execution time. Existing Remote Agents permissions, Agent access, terms acceptance, content checks, and provider/tool policies still apply. + +Stored background Responses require a MongoDB replica set or another verified transaction-capable database. Standalone MongoDB and FerretDB 2.7 do not support this durable feature; ordinary LibreChat remains available. See the [local replica setup](/docs/features/agent_channels#docker-compose). + +Background submission requires an **Agent API key**, including when ordinary inference also accepts OIDC. A management API machine token is not an execution credential. Configure [Remote Agents permissions](/docs/features/agents_api#setup) separately. + +Human tool approval and Ask User pauses require durable MongoDB checkpoint storage. Use the existing [`toolApproval`](/docs/configuration/librechat_yaml/object_structure/agents#toolapproval), Ask User, and [`checkpointer`](/docs/configuration/librechat_yaml/object_structure/agents#checkpointer) settings; background execution does not automatically enable approval or bypass a deny rule. A memory checkpointer does not provide durable background human-action resume. Ordinary requests without a human pause retain their normal execution path. + +Set `DOMAIN_CLIENT` to the trusted public browser URL so generated review links point to your deployment. Do not construct review destinations from incoming Host headers. + +## Submit and poll + +```bash +curl https://chat.example.com/api/agents/v1/responses \ + -H 'Authorization: Bearer YOUR_AGENT_API_KEY' \ + -H 'Content-Type: application/json' \ + -H 'Idempotency-Key: report-request-001' \ + -d '{"model":"AGENT_ID","input":"Summarize the latest report.","background":true,"store":true}' +``` + +Accepted background requests return HTTP `202` with a response object. `background: true` requires storage: omit `store` or set it to `true`. `store: false` and `stream: true` are rejected for background requests. Ordinary synchronous and streaming Responses remain separate supported modes when `background` is omitted or false. + +Use a stable `Idempotency-Key` for retries of the same logical submission. The optional key must be nonblank and at most 256 characters; do not reuse it for a different request. If omitted, the server creates a new request identity, so an ambiguous network retry can create another request. + +```bash +curl https://chat.example.com/api/agents/v1/responses/RESPONSE_ID \ + -H 'Authorization: Bearer YOUR_AGENT_API_KEY' +``` + +Poll the same response until its public status is `completed`, `failed`, `incomplete`, or `cancelled`. `queued` and `in_progress` are not terminal. The server rechecks the original source key and current authority; possession of a response ID is not permission to read or resume it. Use the returned response ID as `previous_response_id` for a subsequent supported continuation rather than resending the original user input. + +A background answer becomes terminal only after the provider has drained and its conversation history is committed with the response. A temporary storage outage keeps the selected outcome pending for recovery without running the provider again. During a rolling upgrade or rollback, keep a compatible worker available to finish pending results; an older worker cannot complete the newer history-bearing receipt format. + +Ordinary synchronous and streaming requests have a different storage contract: if optional saving fails, the generated answer can still succeed with `store: false`. Check that returned value before relying on retrieval or continuation. + +## Review a required action + +A public response can include: + +```json +{ + "status": "in_progress", + "required_action": { + "type": "librechat_review", + "kind": "tool_approval", + "action_id": "ACTION_ID", + "expires_at": 1800000000, + "review_url": "https://chat.example.com/responses/RESPONSE_ID/review" + } +} +``` + +`kind` is `tool_approval`, `question`, or `oauth`. `expires_at` is Unix time in seconds. Treat the action as actionable only while it is present and unexpired. The public projection intentionally omits private approval payloads and provider OAuth URLs. Do not infer that public `in_progress` means a provider is currently running: it also covers a retained action waiting for its owner. + +Open `review_url` in a browser and sign in as the response owner in the correct tenant. The owner can inspect the exact pending tool call, answer a question, or continue an MCP OAuth flow. An unrelated account cannot approve a request just because it can access the same Agent. + +The browser lifecycle uses session authentication, not an Agent API key or a management token: + +| Method | Route | Purpose | +| --- | --- | --- | +| `GET` | `/api/agents/responses/{id}/review` | Read the owner-only review view | +| `POST` | `/api/agents/responses/{id}/review` | Submit the current action decision | +| `POST` | `/api/agents/responses/{id}/cancel` | Request Stop for an owned response | + +Decision submissions include the exact current `actionId` and the payload appropriate to that action. Question submissions use `answer`, or `answers` keyed by the presented question identities. Tool approval submissions use `decisions` containing the exact `tool_call_id` and an allowed `decision` (`approve`, `reject`, `edit`, or `respond`), with the corresponding edited arguments or response text when needed. Use the supplied review UI to respect action-specific validation and offered scopes; an enum value in a transport schema is not a grant to choose an unavailable approval scope. + +A successful decision returns HTTP `202` with `responseId`, `actionId`, `intentId`, and `status` (`queued` or `settled`). This is an acknowledgement, not proof that execution completed. Refresh the canonical review/response state. Do not automatically resubmit an ambiguous decision with a different intent or replay a stale action after a conflict. Resume revalidates current Agent/model/tool/project/file authority and retained execution provenance before privileged work. + +## OAuth recovery limits + +MCP OAuth prompts are retained for owner-only replay until their deadline. Complete authorization through the supplied provider flow; do not submit an approval decision to replace an OAuth callback. A successful callback can continue the same waiting execution while its waiter is still active. + +Durable prompt visibility is not a guarantee that an in-memory OAuth waiter survives a server restart. Durable human question/tool-approval checkpoints and OAuth authorization waiters are different mechanisms. If exact execution state is missing or mismatched, the response reports nonterminal `recovery: "execution_state_unavailable"` and removes stale actionable prompts. This is not proof of completion or cancellation and does not replay work. Sign-in credentials may have been saved even though the original run did not continue. Check external results before retrying; an administrator must reconcile a persistently unknown execution before the held run can safely be cleared. Do not replay an old authorization URL or infer that sign-in restarted the model. See [MCP authentication](/docs/features/mcp#sign-in-with-oauth). + +## Stop and recovery + +API-key clients can request cancellation with `POST /api/agents/v1/responses/{id}/cancel`. Owners can use **Stop** in the review UI. Both paths operate on the same durable response; neither starts a replacement execution. + +A cancellation acknowledgement may still report a nonterminal status while provider drain, checkpoint cleanup, or terminal publication is pending. Continue polling until canonical `cancelled` (or another terminal result already won the race). Stop is not rollback: tools may already have performed external effects. Do not repeat an external operation merely because its answer was cancelled. + +Revoked source keys, changed channel/account bindings, missing authority, stale checkpoint identities, and changed privileged code targets fail closed. Temporary storage outages can delay terminal publication and cleanup; do not force-delete execution proof or manually replay tools to make the status change. diff --git a/content/docs/features/meta.de.json b/content/docs/features/meta.de.json index f5ae5b04c..c721c1cbc 100644 --- a/content/docs/features/meta.de.json +++ b/content/docs/features/meta.de.json @@ -10,6 +10,8 @@ "agent_plugins", "subagents", "agents_api", + "background_responses", + "agent_channels", "artifacts", "code_interpreter", "pull_requests", diff --git a/content/docs/features/meta.es.json b/content/docs/features/meta.es.json index 0eeed4a73..3e7c2bc54 100644 --- a/content/docs/features/meta.es.json +++ b/content/docs/features/meta.es.json @@ -10,6 +10,8 @@ "agent_plugins", "subagents", "agents_api", + "background_responses", + "agent_channels", "artifacts", "code_interpreter", "pull_requests", diff --git a/content/docs/features/meta.fr.json b/content/docs/features/meta.fr.json index a5293817a..f9c55539a 100644 --- a/content/docs/features/meta.fr.json +++ b/content/docs/features/meta.fr.json @@ -10,6 +10,8 @@ "agent_plugins", "subagents", "agents_api", + "background_responses", + "agent_channels", "artifacts", "code_interpreter", "pull_requests", diff --git a/content/docs/features/meta.id.json b/content/docs/features/meta.id.json index b06ad51a7..0feb398a9 100644 --- a/content/docs/features/meta.id.json +++ b/content/docs/features/meta.id.json @@ -10,6 +10,8 @@ "agent_plugins", "subagents", "agents_api", + "background_responses", + "agent_channels", "artifacts", "code_interpreter", "pull_requests", diff --git a/content/docs/features/meta.it.json b/content/docs/features/meta.it.json index 1e6ccf7ac..d1b66fec1 100644 --- a/content/docs/features/meta.it.json +++ b/content/docs/features/meta.it.json @@ -10,6 +10,8 @@ "agent_plugins", "subagents", "agents_api", + "background_responses", + "agent_channels", "artifacts", "code_interpreter", "pull_requests", diff --git a/content/docs/features/meta.ja.json b/content/docs/features/meta.ja.json index d029e41fb..140509deb 100644 --- a/content/docs/features/meta.ja.json +++ b/content/docs/features/meta.ja.json @@ -10,6 +10,8 @@ "agent_plugins", "subagents", "agents_api", + "background_responses", + "agent_channels", "artifacts", "code_interpreter", "pull_requests", diff --git a/content/docs/features/meta.json b/content/docs/features/meta.json index 184105df9..d13ce9856 100644 --- a/content/docs/features/meta.json +++ b/content/docs/features/meta.json @@ -10,6 +10,8 @@ "agent_plugins", "subagents", "agents_api", + "background_responses", + "agent_channels", "artifacts", "code_interpreter", "pull_requests", diff --git a/content/docs/features/meta.ko.json b/content/docs/features/meta.ko.json index c22242753..b1966d1dc 100644 --- a/content/docs/features/meta.ko.json +++ b/content/docs/features/meta.ko.json @@ -10,6 +10,8 @@ "agent_plugins", "subagents", "agents_api", + "background_responses", + "agent_channels", "artifacts", "code_interpreter", "pull_requests", diff --git a/content/docs/features/meta.nl.json b/content/docs/features/meta.nl.json index 5f9343787..a520d382a 100644 --- a/content/docs/features/meta.nl.json +++ b/content/docs/features/meta.nl.json @@ -10,6 +10,8 @@ "agent_plugins", "subagents", "agents_api", + "background_responses", + "agent_channels", "artifacts", "code_interpreter", "pull_requests", diff --git a/content/docs/features/meta.pl.json b/content/docs/features/meta.pl.json index d4c62d1a6..bcc344623 100644 --- a/content/docs/features/meta.pl.json +++ b/content/docs/features/meta.pl.json @@ -10,6 +10,8 @@ "agent_plugins", "subagents", "agents_api", + "background_responses", + "agent_channels", "artifacts", "code_interpreter", "pull_requests", diff --git a/content/docs/features/meta.pt-BR.json b/content/docs/features/meta.pt-BR.json index 3655d7896..766869701 100644 --- a/content/docs/features/meta.pt-BR.json +++ b/content/docs/features/meta.pt-BR.json @@ -10,6 +10,8 @@ "agent_plugins", "subagents", "agents_api", + "background_responses", + "agent_channels", "artifacts", "code_interpreter", "pull_requests", diff --git a/content/docs/features/meta.tr.json b/content/docs/features/meta.tr.json index a671ba18e..4ec97414b 100644 --- a/content/docs/features/meta.tr.json +++ b/content/docs/features/meta.tr.json @@ -10,6 +10,8 @@ "agent_plugins", "subagents", "agents_api", + "background_responses", + "agent_channels", "artifacts", "code_interpreter", "pull_requests", diff --git a/content/docs/features/meta.vi.json b/content/docs/features/meta.vi.json index 134efb300..9c704be71 100644 --- a/content/docs/features/meta.vi.json +++ b/content/docs/features/meta.vi.json @@ -10,6 +10,8 @@ "agent_plugins", "subagents", "agents_api", + "background_responses", + "agent_channels", "artifacts", "code_interpreter", "pull_requests", diff --git a/content/docs/features/meta.zh.json b/content/docs/features/meta.zh.json index df6d457e9..8814dee95 100644 --- a/content/docs/features/meta.zh.json +++ b/content/docs/features/meta.zh.json @@ -10,6 +10,8 @@ "agent_plugins", "subagents", "agents_api", + "background_responses", + "agent_channels", "artifacts", "code_interpreter", "pull_requests",