Chat UI documentation

ML Intern in Slack

Hugging Face's logo
Join the Hugging Face community

and get access to the augmented documentation experience

to get started

ML Intern in Slack

The optional Slack bot runs inside the Chat UI server. Mention @ML Intern in a thread, or send it a direct message. For example:

@ML Intern take the model discussed here and create a Gradio Space for it.

The bot reads that thread, then runs the same ML Intern harness used by HuggingChat. It immediately reacts to accepted requests with 👀 before inference. In new threads, its first reply is a persistent Session status card showing working/waiting/finished/stopped state, elapsed time in roughly 10-second increments, and Jobs spending, reservations, and budget. The Stop control lives on that card. Each new invocation gets its own status card below the invoking message; earlier cards retain their final state. Answering questions or resuming the same turn updates that turn’s card. Public updates, questions, and final results appear below it. Existing threads receive the card at the bottom because Slack cannot insert a new reply above older messages. Successful Space creation exposes a separate link labeled as still in setup. Progress updates show no private model reasoning or raw tool arguments. It shows numbered questions (1, 2, 3) and lettered options (A, B, C) in separate sections without repeating the question below the choices. A single-choice question with up to five options has inline buttons with an explicit confirmation, including the exact amount for budget grants. Forms with two to five single-choice questions and up to five options each also show inline choices. Selections refresh directly after each click and are saved as drafts; Submit answers appears after every question has a selection and confirms them together, including any budget amount. Changing a selection invalidates an older submit button. Longer or multi-select forms use the Answer questions modal. A divider follows the questions and controls; compute totals live in Session status. Inline choices omit that redundant button; an Other answer button remains when custom answers are allowed. After submission, the original question card marks confirmed choices with ✅ and removes its controls. Custom answers appear underneath their question. A divider follows the last answer, and the assistant continues below the card without posting a duplicate answer summary. Previously answered private forms are not retroactively published. Results also appear in the thread. Workspace members can mention the bot in an existing channel thread to participate in the same conversation. The original task owner remains the only person who can approve budgets, submit question forms, or stop or resume the task. Workspace members can use Download trace in a channel thread; the file is posted only to that original thread. DM traces remain private to the DM owner. Participants share that thread’s existing approved budget; other threads and DMs remain separate sessions. In channels, follow-up requests need another mention; in DMs they do not.

Public run updates are retained in a separate, cumulative message in the Slack thread. Earlier results, links, and instructions remain readable as the compact status card changes or becomes a question or final response. Only public assistant text is shown, never private reasoning. Long updates continue in additional messages. Public commentary is published before the question it leads to. After an answer, only new commentary appears below it; earlier text is not replayed. Final results are posted as a new message after progress updates.

Each thread has its own persistent conversation, scripts, compute budget and service registry. MongoDB keeps this state across restarts. A worker lease prevents multiple Chat UI replicas from consuming the same Slack workspace at once. Questions and timed waits resume through the existing server sweepers. Interrupted submissions are not automatically replayed. Retry regenerates the original request, omitting the interrupted assistant turn, and may repeat compute. Inspect existing jobs before confirming Retry. Stop assistant stops the agent, including a parked wait; it does not cancel Jobs already launched on the Hub. The private stop confirmation appears inside that same Slack thread.

This takes the thread/session and Socket Mode approach described in Moon Bot, while reusing ML Intern’s MongoDB state and tool harness instead of creating a separate Pi agent. Download trace uploads the conversation as JSONL to the original Slack thread. Long answers are split into messages; no public conversation share is created.

Thread permissions

The owner is the person who first invoked ML Intern in that thread, which may be different from the person who posted the thread’s first message.

ActionEveryone in the channelOwner only
Mention ML Intern to ask questions or request follow-up work✓
View responses, progress, questions, and confirmed answers✓
Download the thread’s trace✓
Request work using the thread’s existing approved budget✓
Set or change the compute budget✓
Submit answers through question buttons or forms✓
Skip a pending question by sending a new request✓
Stop the assistant✓
Retry an interrupted or failed task✓

“Everyone” means eligible members of the same Slack workspace. Guests, bots, and external shared channels are excluded; the configured channel allowlist also applies. A trace download posts the file only to its original thread.

Each thread has its own conversation and budget. DMs are separate private sessions, with the person messaging the bot as owner. Follow-up requests in channels require another mention. If the assistant is waiting on its own question, a new request from the task owner skips that question and starts a new turn with the shared context and existing budget. The old card is marked as skipped and its buttons are removed. Skipping never grants a budget, and stale answer buttons cannot approve a skipped question. Other participants cannot dismiss the owner’s question. If the model is actively working without a pending question, wait for it to finish or have the owner stop it.

Install in a workspace

  1. Create a Slack app from a manifest, using integrations/slack/manifest.json.
  2. Install it in the workspace and copy its bot token (xoxb-…).
  3. Under Basic Information → App-Level Tokens, generate a token with connections:write (xapp-…). The manifest enables Socket Mode and interactivity, so no public HTTP callback is needed.
  4. Obtain the workspace ID (T…) and invite the bot to the channels where it should work.
  5. Configure the server with the secrets below. Do not commit tokens or send them in a Slack message.
ML_ASSISTANT_MODE=true
USE_USER_TOKEN=true
MCP_FORWARD_HF_USER_TOKEN=true

SLACK_ML_ENABLED=true
SLACK_ML_TEAM_ID=T0123456789
SLACK_ML_BOT_TOKEN=xoxb-your-bot-token
SLACK_ML_APP_TOKEN=xapp-your-app-token
SLACK_ML_HF_TOKEN=hf_your_shared_token
SLACK_ML_BUDGET_USD=0

# Optional: restrict the bot to these channel IDs (also restricts DMs)
SLACK_ML_ALLOWED_CHANNELS=
# Optional: default destination for repos, Spaces, datasets and Trackio dashboards.
# Empty uses the token owner; explicit user destinations override this default.
SLACK_ML_PUBLISH_NAMESPACE=your-company-org
# Optional: choose the compute payer independently of the publishing destination.
SLACK_ML_BILLING_ORGANIZATION=
SLACK_ML_BILLING_RESOURCE_GROUP=

Use the actual workspace ID with no underscore. Configure OPENAI_BASE_URL, OPENAI_API_KEY and ML_ASSISTANT_MODELS as described in local development. The Slack-only deployment uses the shared HF token and does not require the browser OAuth app described in that guide. The HF token needs inference access and permission to use the Hub MCP tools, create Jobs, and write the repositories/Spaces the bot will create. Inference, nested agent calls, Hub tools and background continuations use the configured shared token. The startup model catalog still uses the normal OPENAI_API_KEY configuration.

Run npm run dev, or build with npm run build and start the Node deployment normally. ML_ASSISTANT_MODE must be enabled when building. Use persistent MongoDB or the bucket-backed Space deployment below. Enable the normal ML service poller, service events and parked/answered-question sweepers. The Slack integration is disabled by default and adds no inbound endpoint.

For Hugging Face’s internal phase, provision one shared service-account HF token as SLACK_ML_HF_TOKEN. A dedicated bot deployment is convenient; when sharing a deployment, note that USE_USER_TOKEN=true also applies to browser chats. A different operator-pinned Hub MCP token is rejected at startup to prevent compute being charged to the wrong identity.

Budget and access

The initial budget applies per Slack thread and defaults to $0. If compute is needed, ML Intern can propose a budget option in a question. Selecting that option updates the existing server-enforced ledger before the run resumes. Inference charges are separate. Configure a nonzero default only if everyone allowed to invoke the bot may spend it.

The bot accepts only workspace members, excluding guests, bots, deleted users and external Slack Connect channels. The optional channel allowlist is a further restriction. It reads up to 200 messages from the invoking thread, with a 24,000-character context bound. It does not search other channels, download Slack attachments or read linked Slack threads. If context is missing, provide the model URL or ID explicitly. Slack context and generated artifacts may be sent to the model provider and Hugging Face tools as part of the requested task.

For an existing installation, add the reactions:write bot scope under OAuth & Permissions, then reinstall the app to the workspace to enable the 👀 acknowledgement. Updating the manifest or server alone does not grant the installed token a new scope. If the reaction fails, the request still runs. No separate receipt message is posted.

The Slack app needs the history/read scopes in the manifest to retrieve channel or DM threads, users:read to check membership, chat:write for replies/forms, and files:write for requested traces. See Slack’s Socket Mode guide and thread history API.

Company Slack with a Hugging Face organization

A company can run one dedicated deployment for its Slack workspace, with its own HF credentials. Employees invoke the bot through Slack; they do not each connect an HF account. Their threads have separate conversations and budgets, but all requests use the deployment’s shared HF credential and configured payer.

Choose the credential and organization

Use an organization member’s HF token with the required organization permissions. Membership alone is insufficient: both the member’s role and the token’s scopes must allow the requested operation. See HF access tokens. HF also offers organization-owned service accounts on Enterprise plans. Service-account credentials have not yet been verified end-to-end with this bot; the tested path uses a member token with organization access.

The shared token needs Inference Providers access, access to the Hub MCP tools, permission to run Jobs under the intended payer, and permission to create/write repos and Spaces in the publishing organization. It must not access the state bucket or modify the bot server Space. Use a separate bucket-only SLACK_ML_STATE_TOKEN for persistence and an operator-only SLACK_ML_DEPLOY_TOKEN for server uploads. Check the organization’s billing setup, credits, and hardware quotas for the operations you intend to allow.

Replace your-company-org with the actual HF organization slug:

SLACK_ML_PUBLISH_NAMESPACE=your-company-org
SLACK_ML_BILLING_ORGANIZATION=your-company-org
# Optional resource group for billing; requires the billing organization above.
SLACK_ML_BILLING_RESOURCE_GROUP=
SLACK_ML_STATE_BUCKET=your-company-org/ml-intern-slackbot-state
SLACK_ML_STATE_TOKEN=hf_separate_bucket_only_token

These settings serve different purposes:

SettingPurpose
SLACK_ML_HF_TOKENAuthenticates the bot’s HF requests. It must have access to the organization.
SLACK_ML_PUBLISH_NAMESPACEDefault destination for generated repos, Spaces, datasets and dashboards. Users can explicitly request a different destination if the token permits it.
SLACK_ML_BILLING_ORGANIZATIONSelects the organization used for session billing. Leaving it empty uses the token owner’s billing context. Publishing to an organization does not select it as payer.
SLACK_ML_STATE_BUCKETStores backups of the bot’s conversations and internal state; keep it private.
Server Space ownerOwns and pays for the always-on bot server hardware, independently of each thread’s compute budget.

This is shared service access, not per-employee HF authorization. Only enable channels whose members may use the configured credential. The initial compute budget is per thread, not a company-wide spending cap, and permitted invokers can approve requested budget grants. Inference and server hosting are separate costs.

Install the company’s Slack app

Follow Install in a workspace using the company’s Slack workspace. A workspace administrator may need to approve the app installation. Use that installation’s bot token, app-level token and workspace ID. Create a private testing channel, invite the bot, and initially set SLACK_ML_ALLOWED_CHANNELS to that channel’s ID. Use a dedicated Space and bucket for this workspace; do not share another company’s deployment or credentials.

Continue with Deploy on Hugging Face Spaces. The manual setup is supported today. A centrally hosted, self-service installation flow is described under Other companies and phase two.

Other companies and phase two

This version supports one workspace per deployment. Another company can create the same app from the manifest, run its own instance, and supply its own Slack and HF tokens. Secrets are resolved from server configuration when a run resumes, rather than copied into conversation history or persisted as bot session tokens. Restart the server after rotating credentials.

A hosted, multi-workspace Add to Slack service is a separate deployment phase. It needs Slack OAuth installation storage, encrypted per-workspace HF credentials, tenant-scoped billing and model catalogs, and a web onboarding flow. The current configuration must not be reused as a multi-tenant credential store. Socket Mode apps also cannot currently be listed in Slack’s public Marketplace; choose HTTP events if Marketplace distribution is required. Commercially distributed apps may have different thread-history rate limits than internal apps.

Verification

npx vitest run --project=server src/lib/server/slack
npx vitest run --project=server src/lib/server/generation/__tests__/askResume.spec.ts src/lib/server/generation/parkedSweeper.spec.ts
npm run check

The tests use mocked Slack/model services and an isolated MongoDB. They do not send Slack messages or launch paid compute. A live smoke test still requires an installed Slack app and valid HF credentials: mention the bot in a test thread, give it a model link, answer a clarification, approve a small budget if needed, and verify the returned Space and trace.

Deploy on Hugging Face Spaces

1. Prepare the checkout and Space

Use a checkout containing this integration and run npm ci from its root. Create a private Docker Space under the company organization, for example your-company-org/ml-intern-slackbot-server. Use one replica, upgraded CPU hardware, and disable sleeping (sleep time -1). Keep the Space paused during setup and upload; the helper uploads several commits and does not configure hardware, secrets, or variables for you.

Create an ignored .env.local in the checkout with SLACK_ML_DEPLOY_TOKEN, SLACK_ML_STATE_TOKEN, and SLACK_ML_STATE_BUCKET. Keep the deployment token local; do not install it in the worker Space. The upload helper reads its token from this file. Do not commit it. Creating a Space does not copy your local environment into its settings.

2. Initialize state for a fresh installation

For a new installation with no sessions to migrate, run this command from the checkout root after setting .env.local. It creates a new private bucket and seeds an empty Slack thread collection in the snapshot format the server expects. It refuses to proceed if the bucket already exists, so it cannot silently replace an existing installation’s sessions. This step requires permission to create a bucket in the organization.

node --input-type=module <<'JS'
import { config } from "dotenv";
import { createRepo } from "@huggingface/hub";
import { BSON } from "mongodb";
import { gzipSync } from "node:zlib";
import { writeBackup } from "./integrations/slack/space/state.mjs";

config({ path: ".env.local", quiet: true });
const bucket = process.env.SLACK_ML_STATE_BUCKET;
const token = process.env.SLACK_ML_STATE_TOKEN;
if (!bucket || !token) throw new Error("Set SLACK_ML_STATE_BUCKET and SLACK_ML_STATE_TOKEN");
await createRepo({
  repo: { type: "bucket", name: bucket },
  visibility: "private",
  accessToken: token,
});
const bytes = gzipSync(BSON.EJSON.stringify({
  version: 1,
  createdAt: new Date(),
  collections: [{ name: "slack.threads", documents: [], indexes: [] }],
}, { relaxed: false }));
await writeBackup(bucket, token, bytes);
console.log("Created private bucket and initial state snapshot.");
JS

If upload fails after bucket creation, inspect the bucket before retrying. Do not replace latest.json.gz if a bot has already used that bucket. Choose a new bucket name for a clean retry. Startup deliberately fails when the snapshot is missing or invalid; an empty bucket alone is not sufficient.

For an existing installation, skip the empty seed. Stop its worker and migrate its existing Slack conversations and related state using snapshot/writeBackup in integrations/slack/space/state.mjs. Include synthetic Slack users, thread/input/ delivery records, budgets, questions, waits, files and registry data, while excluding unrelated browser sessions and OAuth credentials. This is a programmatic migration, not an automatic import wizard; verify a restore before retiring the original database.

3. Configure Space settings

In the Space’s Settings, add these secrets:

SecretValue
SLACK_ML_HF_TOKENThe shared HF token with organization access.
OPENAI_API_KEYThe same HF token, used for the startup model catalog.
SLACK_ML_BOT_TOKENThe company’s installed Slack bot token (xoxb-…).
SLACK_ML_APP_TOKENThe Slack app-level Socket Mode token (xapp-…).

Add the following variables, replacing the example organization, Space slug, workspace ID and channel ID with your actual values:

ML_ASSISTANT_MODE=true
USE_USER_TOKEN=true
MCP_FORWARD_HF_USER_TOKEN=true
ENABLE_CONFIG_MANAGER=false
OPENAI_BASE_URL=https://router.huggingface.co/v1
ML_ASSISTANT_MODELS=[{"id":"zai-org/GLM-5.3-Flash","provider":"together"},{"id":"moonshotai/Kimi-K3","provider":"baseten"}]
MCP_SERVERS=[{"name":"Hugging Face","url":"https://hf.co/mcp?login"}]
ML_ASSISTANT_SERVICE_POLLER=true
ML_ASSISTANT_SERVICE_EVENTS=true
SLACK_ML_ENABLED=false
SLACK_ML_TEAM_ID=T0123456789
SLACK_ML_ALLOWED_CHANNELS=C0123456789
SLACK_ML_BUDGET_USD=0
SLACK_ML_PUBLISH_NAMESPACE=your-company-org
SLACK_ML_BILLING_ORGANIZATION=your-company-org
SLACK_ML_STATE_BUCKET=your-company-org/ml-intern-slackbot-state
SLACK_ML_STATE_TOKEN=hf_separate_bucket_only_token
MONGODB_DB_NAME=ml-intern-slackbot
PUBLIC_ORIGIN=https://your-company-org-ml-intern-slackbot-server.hf.space

Use the actual Space app URL for PUBLIC_ORIGIN. For JSON variables, paste the JSON value directly into Space settings, without extra shell quotes. Select models and providers available to your account; the examples match the local development guide. Do not pin a different token inside MCP_SERVERS. This Docker image starts its own loopback MongoDB and sets MONGODB_URL internally; no external MongoDB account or secret is required.

4. Upload and start

From the checkout root:

node integrations/slack/space/upload.mjs your-company-org/ml-intern-slackbot-server

The helper uploads tracked source files in batches, excludes local env files, and checks for configured credentials. It installs integrations/slack/space/Dockerfile and integrations/slack/space/README.md as the Space’s root Dockerfile and README. The Dockerfile enables ML Intern at build time.

After upload completes and any old worker has stopped, set SLACK_ML_ENABLED=true in Space settings and resume the Space. Check runtime logs for [state] Restored, [slack] ML Intern connected via Socket Mode, and subsequent [state] Backup saved messages. The Space restores from the bucket before starting the bot.

5. Verify and operate

In the allowed test channel, mention the bot in two separate threads. Ask for a clarifying question in one and a short answer in the other; the second must finish while the first waits. Answer the question with Answer questions and verify that each response stays in its own thread. Check an explicitly approved small compute task separately to validate the organization’s Jobs permissions and payer.

Wait for a completed backup, pause and resume the Space, and confirm an existing thread still works. Verify a pending question can be answered after restart. Then expand the channel allowlist if desired. Keep the local worker stopped.

Snapshots use consistent snapshot reads and stream compressed records without locking application writes. BSON IDs, dates, and indexes survive restore.

Backups run every 30 seconds after the preceding upload completes and on graceful shutdown. An abrupt crash can lose changes since the last completed backup. Backup failures retain the live database and back off retries; monitor the logs. This is a single-replica pilot, not a lossless database service. Pause the Space before deploying/restarting it and wait for shutdown to finish; never run two replicas with independent local databases against this bucket. A MongoDB lease cannot coordinate independent databases.

Do not connect workers using different databases to the same Slack app concurrently. The shared-database lease protects replicas using the same MongoDB, but independent Space databases cannot coordinate through it. Pause the Space and allow its final backup to finish before every source upload or deployment update.

Attachments

Files attached to the invoking message or earlier human messages in the same Slack thread are downloaded with the bot token and attached to the ML Intern conversation through Chat UI’s upload pipeline. This supports image requests such as OCR. Files posted after the invocation and bot-uploaded files are excluded. Limits are 10 distinct files, 10 MB per file, and 25 MB total per invocation. Start a fresh thread to select a smaller set. Download failures are reported privately in the thread rather than silently running without the image.

The app requires the files:read bot scope. For an existing installation, add it under OAuth & Permissions → Bot Token Scopes, then reinstall the app to your workspace. Updating the repository manifest alone does not grant permission to an already-installed bot. Attachments stay in the conversation’s file storage; they are not made public on Slack.

State isolation and backup format

SLACK_ML_STATE_TOKEN must be a separate fine-grained token with access only to the private state bucket. Restrict the compute token so it cannot read or write that bucket, even if both tokens belong to the same account. Merely issuing two broad tokens does not isolate state. Startup rejects identical tokens and rejects compute credentials that can read the seeded snapshot. The state token is removed from the Chat UI worker’s environment and is never forwarded to tools. Existing installations must configure this new secret before deploying this change.

The Space starts a local single-node MongoDB replica set for snapshot reads. Backups use one snapshot read timestamp, stream individual BSON-preserving EJSON records through gzip into a temporary file, and upload from disk. There is no global write lock or database-sized string. Temporary files are removed after upload. Version 1 seeds/backups remain readable for migration; the first successful backup rewrites them as version 2. Legacy version 1 import still requires memory for its single JSON document. When using snapshot/readBackup directly, their result is now a temporary file path; call removeBackup(path) in a finally block after use.

Backup failures retain the running database and retry with increasing delay instead of restarting into an older snapshot. Operators must monitor [state] Backup failed logs; a crash while backups are failing can still lose data. Local disk must have room for the compressed snapshot.

Thread delivery retries back off and pause after five consecutive failures; a new mention re-enables delivery. Polling advances even when delivery fails, and inactive threads with no pending inputs are unwatched. Status animation slows as watched-thread count grows, targeting at most 30 status updates/minute across the workspace (other Slack calls also consume rate limits). Delivery records are retained to prevent replay; an index supports question-card lookups. Follow-ups quote only human context since the last successfully submitted invocation; long initial threads keep the most recent 200 human messages.

Update on GitHub