A 30-minute hands-on workshop. By the end, you'll have a Slack bot that:
- Receives messages in your workspace
- Searches the live web via Tavily
- Runs inference on open-source models via Nebius Token Factory
- Reasons + replies — all from a single Docker container on your laptop
Time budget (~30 min)
- Prereqs (5 min) — install Docker, clone repo
- Token Factory key (3 min)
- Tavily key (2 min)
- Slack app (10 min)
- Launch + verify (5 min)
- Mention the bot (5 min)
Prefer to have your AI coding agent drive this? Paste
AGENT-PROMPT.mdinto Claude Code, Cursor, Codex, or Aider and it'll run the whole workshop interactively — pausing for keys, verifying each step, and recovering from common errors.
Open these in tabs — you'll need them in roughly this order:
| Tab | URL | What you'll do |
|---|---|---|
| 1 | https://tokenfactory.nebius.com | Get the Nebius key |
| 2 | https://app.tavily.com | Get the Tavily key |
| 3 | https://api.slack.com/apps | Create the Slack app |
| 4 | Your Slack workspace | Invite the bot |
You'll need:
- A Slack workspace where you can install an app (your own, a sandbox, or a community one with admin permission)
- A terminal — macOS Terminal, iTerm, GNOME Terminal, anything
No Node, no Python, no other tooling required. Everything runs inside Docker.
# macOS
brew install --cask docker
# Linux (Debian/Ubuntu)
curl -fsSL https://get.docker.com | sh
sudo usermod -aG docker $USER # log out + back in after thisVerify:
docker --version # → Docker version 24.x or newer
docker compose version # → Docker Compose version v2.x or newerOn macOS, launch Docker Desktop once after install — the menu-bar whale must be steady (not animating) before continuing.
Checkpoint:
docker run --rm hello-worldprints "Hello from Docker!" → ready.
git clone https://github.com/opencolin/openclaw-nebius.git
cd openclaw-nebius/workshop
cp .env.example .envYou should now have:
workshop/
├── docker-compose.yml ← we'll docker compose up this
├── .env ← you're about to fill this in
├── .env.example ← template, ignore from here on
└── WORKSHOP.md ← this file
Pre-pull the image so the rest of the workshop is instant:
docker pull ghcr.io/opencolin/openclaw-workshop:latestCheckpoint: the pull finishes with
Status: Downloaded newer image for …. If pull fails (image not yet public), usedocker compose buildinstead — adds ~2 min.
Open .env in any editor. You'll fill in five values across the next three blocks.
Nebius Token Factory is an OpenAI-compatible API that hosts 60+ open-source models on Nebius GPUs. Your agent runs locally; only the model inference hits the API.
-
Tab 1 → https://tokenfactory.nebius.com → sign in (or sign up — it takes 30 sec)
-
Sidebar → API keys → Create API key → give it a name → copy it (starts with
v1.) -
In your
.env:NEBIUS_API_KEY=v1.…
US tenants only: change TOKEN_FACTORY_URL to https://api.tokenfactory.us-central1.nebius.com/v1.
curl -s https://api.tokenfactory.nebius.com/v1/models \
-H "Authorization: Bearer $(grep ^NEBIUS_API_KEY .env | cut -d= -f2)" \
| head -c 200Checkpoint: you see JSON with model IDs like
zai-org/GLM-5,moonshotai/Kimi-K2.6.
Tavily is search-for-agents: LLM-ready results, no HTML parsing, prompt-injection filtering built in.
-
Tab 2 → https://app.tavily.com → sign in / sign up
-
Sidebar → API Keys → Create new key → copy it (starts with
tvly-) -
In your
.env:TAVILY_API_KEY=tvly-…
Checkpoint: key is pasted; we'll verify end-to-end once the bot is talking.
This is the longest block — Slack requires a real OAuth app, but with the manifest below it's mostly copy/paste.
Tab 3 → https://api.slack.com/apps → Create New App → From a manifest → pick your workspace.
Paste this YAML and click Next → Create:
display_information:
name: OpenClaw Workshop Bot
description: Agentic assistant powered by Nebius Token Factory and Tavily
background_color: "#0F172A"
features:
bot_user:
display_name: OpenClaw Workshop Bot
always_online: true
oauth_config:
scopes:
bot:
- app_mentions:read
- channels:history
- channels:read
- chat:write
- groups:history
- groups:read
- im:history
- im:read
- im:write
- mpim:history
- mpim:read
- mpim:write
- users:read
settings:
event_subscriptions:
bot_events:
- app_mention
- message.channels
- message.groups
- message.im
- message.mpim
socket_mode_enabled: trueWhy no
assistant:write? It puts the app in Slack's AI Assistant surface mode and silently blockschat:writefrom being granted alongside it. If you want the assistant tab later, add it back as a separate app — the workshop bot needs plain chat.
Checkpoint: you land on the new app's settings page. The left sidebar shows "Basic Information", "App Home", "OAuth & Permissions", etc.
This is the xapp-… token. Socket Mode means the bot opens a WebSocket out to Slack — no public URL needed.
- Basic Information → scroll to App-Level Tokens → Generate Token and Scopes
- Name:
workshop-socket - Add scope:
connections:write - Generate → copy the
xapp-…token
In .env:
SLACK_APP_TOKEN=xapp-…- Install App in the left sidebar → Install to Workspace → Allow
- After install, the Bot User OAuth Token appears (starts with
xoxb-…). Copy it.
In .env:
SLACK_BOT_TOKEN=xoxb-…Checkpoint:
.envhas four real values:NEBIUS_API_KEY=v1.…TAVILY_API_KEY=tvly-…SLACK_BOT_TOKEN=xoxb-…SLACK_APP_TOKEN=xapp-…
If anything Slack-side feels off, the full Slack settings reference covers every panel, scope, token type, and install-vs-reinstall trap — captures the entire debugging arc that produced this workshop.
From openclaw-nebius/workshop:
docker compose upWatch the logs. Three good signals will scroll by, in this order:
✓ Wrote /home/node/.openclaw/openclaw.json
✓ Plugins ready
[gateway] http server listening (9 plugins: … slack … tavily; …)
[slack] [default] starting provider
[slack] socket mode connected
[gateway] ready
If you see all of those, the bot is online.
Checkpoint: the last log line in your terminal mentions
socket mode connectedand[gateway] ready. If it isn't there, scroll up and look for a red error — usuallyinvalid_auth(wrong token) or401(bad Nebius/Tavily key).
You'll also see this line — copy it, you'll use the URL to view the OpenClaw dashboard:
>> Dashboard: http://localhost:28789/#token=<random>&gatewayUrl=ws://localhost:28789
Why port 28789 and not 18789? OpenClaw's default gateway uses 18789 on
127.0.0.1. If you already run a localopenclaw gateway, it grabs that port first — so the workshop container deliberately maps to 28789 on your host to coexist. Inside the container it's still 18789.
In your Slack workspace, in any channel (or create a fresh #openclaw-test):
/invite @OpenClaw Workshop Bot
@OpenClaw Workshop Bot search the web for what's new from Anthropic this week and summarize in 3 bullets
The bot will:
- Pick up the mention (Socket Mode pushes the event from Slack to your container)
- Decide it needs
tavily_searchto answer - Call Tavily
- Send the results + your question to Nebius Token Factory
- Reply in the thread
Watch the same terminal — you'll see [tavily], [tokenfactory], and [slack] log lines as it works.
Checkpoint: a real reply appears in your Slack thread within 10–30 seconds. If it doesn't,
docker compose logs -f openclaw | tail -50is your friend.
OpenClaw ships with a BOOTSTRAP.md that fires on the first conversation a fresh agent ever has. So the very first reply from your bot will likely be something like:
Hey. I just came online. Who am I? Who are you?
I woke up in this workspace and there's a
BOOTSTRAP.mdtelling me to figure out who I am. So… here we are. What should we call me? What kind of creature am I — AI assistant, ghost in the machine, something weirder?
This is expected — it's not an error. Reply in the thread with the identity you want it to assume. Example:
You're the OpenClaw Workshop Bot. I'm <your name>, the workshop instructor.
You're an agent powered by Nebius Token Factory (for reasoning) and Tavily
(for web search). Use tavily_search whenever fresh information would help;
otherwise reply directly. Be concise.
The bot will lock that identity in and behave like a normal assistant from then on. From here on the responses skip the bootstrap.
Mix tool-use with reasoning to see what each model is good at:
@OpenClaw Workshop Bot search for current NVIDIA stock price and compare to last week
@OpenClaw Workshop Bot what's the best open-source LLM for code generation right now?
@OpenClaw Workshop Bot summarize https://docs.tavily.com in 5 bullets
Edit .env and change the OPENCLAW_MODEL= line to any of the IDs in the table below, then:
docker compose down && docker compose up -dNote:
openclaw config set agents.defaults.model.primary …works, but only until the next container restart — the entrypoint regeneratesopenclaw.jsonfromOPENCLAW_MODELevery time the container starts. Persistent changes go in.env.
The container ships with these 10 models pre-listed (matching the slide deck's recommended set):
| Model | When to reach for it |
|---|---|
tokenfactory/moonshotai/Kimi-K2.6 |
Default. Long context (262K), good at reasoning |
tokenfactory/deepseek-ai/DeepSeek-V4-Pro |
1M context, structured output |
tokenfactory/nvidia/Nemotron-3-Nano-Omni |
Cheapest — $0.06/$0.24 per 1M tokens |
tokenfactory/zai-org/GLM-5.1 |
Strong general-purpose chat |
tokenfactory/MiniMaxAI/MiniMax-M2.5 |
Cheap + fast |
tokenfactory/Qwen/Qwen3.5-397B-A17B-fast |
Throughput-optimized Qwen |
tokenfactory/NousResearch/Hermes-4-405B |
Best for agentic tool-use |
tokenfactory/openai/gpt-oss-120b |
OpenAI's open weight release |
Paste the Dashboard: URL from your docker compose up logs into a browser. You'll see live conversations, tool calls, and can change config from the UI.
docker compose downYour .env stays — restart with docker compose up any time.
✅ A Slack bot connected via Socket Mode (no public URL) ✅ Inference routed through Nebius Token Factory (no GPU on your laptop) ✅ Web search through Tavily (fresh, agent-safe) ✅ One container, four env vars, everything else config-as-code
Same shape works for Microsoft Teams, Discord, Telegram, WhatsApp — swap the channels.slack block in config/openclaw.json.template.
| Symptom | Fix |
|---|---|
docker pull says unauthorized or not found |
The published image isn't ready yet — docker compose build builds locally instead. Adds ~2 min. |
SLACK_BOT_TOKEN must start with 'xoxb-' |
Bot/app tokens swapped. xoxb- = bot token, xapp- = app-level token. |
Gateway failed to start: Invalid config |
Edit .env and re-run docker compose up — usually a stray quote or extra space in a key. |
Changed .env but the container still uses the old values |
docker restart does NOT reload --env-file — it freezes env vars at create time. Run docker compose down && docker compose up -d. If you started with raw docker run, do docker stop workshop-test && docker rm workshop-test && docker compose up -d. Verify with docker exec workshop-test bash -c 'echo ${SLACK_BOT_TOKEN: -8}' — last 8 chars should match .env. |
[slack] invalid_auth or account_inactive |
App is uninstalled in the workspace, or you pasted a token from a previous install. Open https://api.slack.com/apps/<APP_ID>/install-on-team → Install to Workspace → copy the freshly-shown xoxb-…. |
[slack] missing_scope; needed: chat:write |
The bot was installed before chat:write was added — Slack's "Reinstall" silently keeps the old scope set on the existing token. Fully uninstall from https://<workspace>.slack.com/apps/manage (find the bot → Remove App), then install fresh. The new install issues a token bound to the current scope set. |
Slack manifest has chat:write but Slack still won't grant it |
assistant:write in the manifest can put the app in "Slack AI Assistant" mode and silently block plain chat scopes. Edit the manifest, remove assistant:write (and assistant_thread_* events), save, uninstall, install fresh. |
| Bot silent in Slack after mention | (a) bot isn't in the channel — /invite @your-bot-name; (b) Socket Mode toggle is off — turn it on in Socket Mode; (c) check docker logs -f workshop-test | grep -i slack |
401 Unauthorized from Token Factory |
Verify the key and that TOKEN_FACTORY_URL matches your region (US tenants use …us-central1…). |
| Dashboard URL shows "device identity" error | Token must be in URL hash (#token=…), not query. The startup log line gets this right — copy it verbatim. |
| Workspace admin policy strips Slack scopes (rare) | Some workspaces have an "admin approval required" policy that silently drops un-approved scopes from the bot token. Easiest workaround: create a free sandbox workspace at https://slack.com/create and install the app there. |
- Long-term memory:
openclaw plugins install @mem0/openclaw-mem0— the bot remembers across conversations - Deploy to the cloud: see
nebius-skill/examples/deploy-openclaw.md— same image, hosted on Nebius - Tighten Slack access: see
SLACK-APP-SETUP.md→ "Tightening down for production" — restrict to specific channels/users - Add more channels: Microsoft Teams, Discord, WhatsApp —
openclaw channels list --allshows the catalog
Come hang out on the OpenClaw Discord. See you at the next webinar — Building More Secure Autonomous AI Agents with NemoClaw on Nebius.