Slack

The Omnigent Slack integration lets your team talk to agents directly from Slack. Mention the bot in a channel or DM it, and it starts an Omnigent session; replies stream into the thread live, and tool approvals show up as interactive Approve / Deny cards you can answer in place.

The core model is simple: one Slack thread ↔ one Omnigent session. Replying in a thread continues the same session; a new mention starts a new one.

The bot connects to a single Omnigent server chosen by the operator — users never enter a server URL. Each user still authenticates as their own Omnigent identity, so sessions run with per-user permissions and no shared credential ever passes through Slack.

What you can do

Setting it up (operator)

The bot is a Socket Mode Slack app — it opens an outbound WebSocket to Slack, so it needs no public HTTP endpoint and no request URLs.

1. Create the Slack app

Quick path — create the app from the manifest. At api.slack.com/apps → Create New App → From an app manifest, pick the workspace, and paste integrations/slack/deploy/slack-app-manifest.yaml from the repo. That configures Socket Mode, Interactivity, every scope and event the bot needs, and the /omnigent command in one step. For Databricks web-auth, uncomment users:read and users:read.email in the manifest before pasting it. A manifest cannot mint tokens, so finish with the two steps that a manifest can't do — generate a connections:write app-level token (Basic Information → App-Level Tokens) and Install to Workspace for the bot token — then skip to step 2 below only if you need to double-check the scopes.

Doing it by hand instead:

  1. Create a Slack app with Socket Mode and Interactivity enabled (Socket Mode also delivers the button/modal payloads — no request URL needed).
  2. Add the OAuth scopes and event subscriptions listed below.
  3. Add a slash command /omnigent (Features → Slash Commands). In Socket Mode the request URL is ignored, so any placeholder works.
  4. Install the app into your workspace.

2. Grant scopes

These scopes and events are mirrored in integrations/slack/deploy/slack-app-manifest.yaml — if you created the app from the manifest they're already set, and if you change one, change both together.

Bot token scopes (OAuth & Permissions → Bot Token Scopes) — all required:

ScopeWhy
app_mentions:readReceive @mentions — how the bot joins a channel thread.
chat:writePost, delete, and stream replies (including ephemeral nudges).
im:writeOpen a DM to send the setup button and logout confirmation.
im:historyRead DMs — DMs are a first-class entry point.
commandsRegister and receive /omnigent.
team:readRead the workspace name to label the login request.

Add two more bot scopes only for Databricks web-auth mode, where the bot signs the user's email into the enrollment link and matches it against the OAuth-authenticated identity; omit them in accounts / oidc mode:

ScopeWhy
users:readRead the user's profile to look up their email.
users:read.emailRead the user's email address for identity binding.

Channel history scopes — add only for the channel types where the bot will run: channels:history (public), groups:history (private), mpim:history (group DMs). If you only use DMs and channel @mentions, you can omit all three.

App-level token scope: connections:write (required for the Socket Mode connection).

Event subscriptions (Subscribe to bot events): app_mention, message.im, and message.channels / message.groups / message.mpim for the channel types you enabled above.

3. Install and configure the bot

The bot ships as the slack extra of Omnigent (the separate omnigent-slack package), installed in the same environment as the omni CLI so omni integration slack can find it:

uv tool install "omnigent[slack]"    # or, from a source checkout: uv sync --extra slack

Configuration comes from real environment variables — the bot does not read a .env file itself (matching omni server). Export the variables directly, or launch the bot under a tool that injects a .env (e.g. uv run --env-file .env omni integration slack). .env.example documents the full set to copy from:

VariableRequiredPurpose
OMNIGENT_SLACK_BOT_TOKENYesThe bot token (xoxb-…).
OMNIGENT_SLACK_APP_TOKENYesThe app-level token (xapp-…) for Socket Mode.
OMNIGENT_SERVER_URLYesThe one Omnigent server the bot talks to.
OMNIGENT_DEVICE_CLIENT_SECRETNoSet to the same value as the server's OMNIGENT_DEVICE_CLIENT_SECRET so only this bot can drive the device-grant login. Accounts mode only.
OMNIGENT_SLACK_TOKEN_ENCRYPTION_KEYNoA Fernet key that encrypts users' delegated tokens at rest. Without it, tokens live in memory only and users re-authenticate after a restart.
OMNIGENT_SLACK_DATABASE_PATHNoWhere the bot's SQLite store lives. Defaults to $OMNIGENT_DATA_DIR/omnigent_slack.sqlite3 (or ~/.omnigent/omnigent_slack.sqlite3).
OMNIGENT_SLACK_DEFAULT_AGENT_IDNoPre-selects this agent id in every user's /omnigent setup modal. See Operator-set setup defaults.
OMNIGENT_SLACK_DEFAULT_HOST_TYPENoOnly managed — pre-selects the server-provisioned managed sandbox as the host in the setup modal. Any other non-blank value fails at startup.
LOG_LEVELNoSet to DEBUG when diagnosing why Slack events aren't producing replies.

Generate an encryption key with:

uv run python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"

If the Omnigent server runs as a Databricks App (header/proxy auth), the bot uses its own Databricks web-auth flow instead of the device/OIDC login — see Databricks Apps web-auth below for the extra OMNIGENT_SLACK_DATABRICKS_* variables it needs.

4. Run it

The bot runs as a background daemon managed by the omni CLI:

omni integration slack               # run in the foreground (Ctrl-C to stop)
omni integration slack --background  # run in the background (detached)
omni integration slack status        # is the background bot running?
omni integration slack stop          # stop the background bot
omni integration slack logs          # print the log path
omni integration slack logs -f       # follow the log

Running --background while the bot is already up is a no-op that reports the existing process. A foreground run refuses to start if a background daemon already holds the socket (stop it first).

Per-user setup

The first time someone interacts with the bot without having configured it, the bot DMs them a Set up Omnigent button. The button opens a modal that:

  1. Validates connectivity to the operator's server, and — if the server has authentication enabled — walks the user through login (see below).
  2. Lets them pick an agent and host (both required, populated from the server) and a workspace path — the absolute directory on the host where each session's runner starts. It defaults to the host's home directory.

The choice is saved per (Slack workspace, user). Run /omnigent any time to change agent, host, or workspace, and /omnigent logout to revoke your token and clear all saved settings.

Each new session launches a fresh runner on the chosen host, rooted at the configured workspace.

Operator-set setup defaults

If your team has standardized on one agent — or on the managed sandbox — the operator can pre-select those choices in step 2 for everyone, so a new user just submits the modal instead of making two decisions they have no opinion on:

VariableEffect
OMNIGENT_SLACK_DEFAULT_AGENT_IDOpens the Agent menu on this agent id — one of the ids the server returns from GET /v1/agents.
OMNIGENT_SLACK_DEFAULT_HOST_TYPEOnly managed — opens the Host menu on the server-provisioned managed sandbox. Any other non-blank value fails at startup.

Both are set in the bot's environment like every other variable. Either may be left unset or blank for no default (the picker then opens blank, as it always has).

These are pre-selections, nothing more. The modal still opens, still shows the full menus, and still has to be submitted — the user can change either choice, and what they submit is what is saved.

Availability is checked per user, each time the modal is rendered, against the agents and hosts that user's own login can see. A default that isn't on offer — an agent id missing from their menu, or managed on a server that provisions no sandbox — leaves that menu blank and adds a warning note in the modal saying so. A different agent or host is never substituted, and a valid default in the other menu is still applied. The note distinguishes what it could establish from what it couldn't: if the server's sandbox capability couldn't be checked, the modal says the check didn't complete rather than claiming the server provisions none; and an agent past the menu's 100-option cap is reported as missing from the menu, not from the server.

There is deliberately no default for a specific external host id: on an authenticated server /v1/hosts is owner-scoped, so one user's host is never listed for anyone else and could not be pre-selected.

Authentication

For servers with authentication enabled, each Slack user logs in with their own Omnigent identity inside the /omnigent modal. The bot auto-detects the server's auth mode and picks the matching flow:

Tokens are stored encrypted at rest when OMNIGENT_SLACK_TOKEN_ENCRYPTION_KEY is set; otherwise they're kept in memory only and lost on restart (users simply re-authenticate).

Databricks Apps web-auth

When the Omnigent server is a Databricks App, its proxy authenticates every request and injects the user's identity — a Socket-Mode event carries no such proxy-authenticated request, so the device/OIDC flows can't be driven. Instead the bot runs a custom U2M OAuth app (authorization code + PKCE, offline_access) through an enrollment page it serves as its own Databricks App:

  1. On /omnigent, the bot looks up the user's email, generates a PKCE verifier and single-use nonce, and posts a Sign in with Databricks link to the workspace /oidc/v1/authorize.
  2. The user signs in; Databricks redirects back to the bot's /auth/callback with a single-use, PKCE-bound code, which the bot exchanges for an access + refresh token pair.
  3. Identity binding — the callback requires the OAuth-authenticated email to equal the Slack email signed into the link, so a link bound to one user can't store another's token. A mismatch is refused.
  4. Confirm before storing — the callback shows a consent page naming the exact identities being linked; the token is persisted only when the user submits the confirming button, then the setup modal advances automatically.

The bot presents the access token to the server; the proxy validates it and injects the real identity header, so no server-side change is needed. It refreshes silently, so the user signs in once, not hourly.

Enable it with OMNIGENT_SLACK_SERVER_AUTH=databricks plus these variables:

VariableRequiredPurpose
OMNIGENT_SLACK_DATABRICKS_CLIENT_IDYesThe custom U2M OAuth app's client id (public).
OMNIGENT_SLACK_DATABRICKS_CLIENT_SECRETYesThe custom U2M OAuth app's client secret.
OMNIGENT_SLACK_DATABRICKS_STATE_SECRETYesHMAC key signing the enrollment state (≥32 chars of entropy, e.g. openssl rand -hex 32). Its own secret, separate from the OAuth client secret.
OMNIGENT_SLACK_DATABRICKS_APP_URLYes¹Public base URL of the bot's own Databricks App — the enrollment link base and OAuth redirect base. Not platform-injected, and the URL only exists after the first deploy, so it's wired in on a second deploy pass.
OMNIGENT_SLACK_DATABRICKS_SCOPESNoRequested scopes (space-separated). Defaults to all-apis; must be a superset of the server app's scopes. openid + offline_access are always added.

¹ Not enforced at startup — the bot boots without it, but no enrollment link is issued until it's set. The enrollment web server always binds DATABRICKS_APP_PORT (8000 by convention, platform-injected); there's no override knob.

Register the custom OAuth app's redirect URI as <app-url>/auth/callback. To deploy the bot as its own Databricks App, see integrations/slack/deploy/databricks/README.md in the repo.

Approvals and questions

When the agent needs you — a tool-call approval or a multiple-choice question — it appears in the thread:

Cards are visible to the whole channel, but only the thread owner can act on them. Answering from the web UI works too: the card in Slack finalizes automatically once the request is resolved anywhere.

Sharing a channel with your team

Slack channels are multi-user, so the bot enforces a per-thread owner model: a thread belongs to whoever started it. If someone else replies in your thread, they get a private note pointing them to start their own thread — they are never added to your session.

If you message a thread while the bot is still replying, it privately asks you to wait (or continue in the web UI). A message to an idle thread just continues the conversation.

Supported and not supported

Supported

Not supported

Troubleshooting