OpenTag 讓 Slack 對話變動作
我拆 OpenTag 的 Slack agent 模板,重點是讀 thread、接工具、做富結果,最後還要人類按批准才執行。

我拆 OpenTag 的 Slack agent 模板,重點是讀 thread、接工具、做富結果,最後還要人類按批准才執行。
我玩 Slack bot 玩到有點煩了。大多數都長一樣:你丟一句,它回一句,最多幫你把 thread 摘要一下,然後就沒了。看起來很像有做事,實際上只是把對話包裝得比較像樣。真正卡人的地方從來不是「會不會回訊息」,而是它能不能讀上下文、去查工具、把結果整理好,最後還知道什麼時候該停下來等人按一下批准。少了這個,bot 不是太膽小,就是太莽。
這次我會拆的是 OpenTag。它不是那種可愛的 Slack 小玩具,而是 CopilotKit 做的一個 self-hosted 模板,目標很明確:讓 agent 在 Slack 裡讀 thread、呼叫工具、渲染更豐富的輸出,然後在真正動手前先卡一個 human approval gate。這個 repo 目前有 554 stars、66 forks,數字不算誇張,但夠我確定一件事:大家都在找同一種工作流,只是大多數人還沒把它做對。
別再把 Slack bot 當自動回信機
訂閱 AI 趨勢週報
每週精選模型發布、工具應用與深度分析,直送信箱。不定期,不騷擾。
不會寄垃圾信,隨時可取消。
Run your own AI agent inside Slack: it reads a thread, answers, calls your tools, and renders rich results right in the conversation.
白話翻譯就是:這不是一個只會吐字的聊天機器人,而是一個住在 Slack 裡的工作流程引擎。它先看 thread,再決定要不要查資料、叫外部系統、或者把結果直接塞回對話裡。這跟那種「/summarize」指令 bot 完全不是同一個層級。

我以前也踩過這種坑。內部支援頻道想做一個 bot,讓它幫忙看 incident thread,再順手查 Jira。結果它不是亂編 ticket number,就是乾脆裝死。那時候我才認清楚,問題不是 prompt 不夠長,而是架構根本沒給它做事的能力。你把它做成只能回答的東西,它就永遠只會回答。
OpenTag 的重點是 agent loop,不是 chatbot。Slack 訊息只是入口,真正的工作在 runtime 裡。這件事很重要,因為它讓 bot 有機會對流程有意見,而不是只對語言有意見。換句話說,它可以先判斷,再行動,而不是每次都把責任丟回人類。
實操寫法很簡單:先把你的 Slack bot 拆成三件事,摘要、查詢、動作。很多團隊只做到摘要就停了,然後就說「bot 很方便」。不,它只是省掉你複製貼上而已。你要的是能推進工作的 agent,不是更會講話的回覆機器。
self-hosted 不是附加價值,是這套玩法的核心
Think of it as having Claude in your workspace, except open-source and self-hosted: you own the runtime, bring your own model, and wire it to your own tools.
翻譯一下就是:你不是在租一個黑盒服務,你是在把 runtime 掌握在自己手上。模型你自己選,工具你自己接,資料流向也自己管。對很多團隊來說,這才是能不能上線的分水嶺。因為一旦 Slack 裡面開始碰敏感 thread,資安、合規、權限這些麻煩事就會自己冒出來。
我很討厭那種把 self-hosting 講得很輕鬆的文章。好像只要貼一個 docker-compose 就萬事大吉。實際上,agent 的麻煩從來不是模型本身,而是周邊:連線、重試、持久化、權限、觀測、秘密管理。OpenTag 至少沒有裝傻,它很直接地告訴你:要控制權就自己跑,要省 ops 就等別的產品形態。
這種誠實我反而買單。因為我見過太多 starter kit 把部署複雜度藏起來,等你真的接進公司流程,才發現 bot、runtime、storage 全黏在一起,改一個地方就像拆炸彈。self-hosted 的價值不是便宜,是你能決定邊界在哪裡。
- 如果你們有 compliance 顧慮,self-hosted 是必要條件,不是加分項。
- 如果你們只想先驗證流程,這個 repo 也可以當 reference implementation。
- 如果你們之後想換部署方式,先把架構拆開,之後才有退路。
實操寫法:先問自己,這個 bot 是產品還是模板。是產品,就從第一天把 hosting、secret、persistence 一起算進去。是模板,就先複製架構,別急著把所有東西都做成正式服務。
真正值錢的是薄薄一層組裝,不是大而全框架
OpenTag is a thin layer on top of a handful of CopilotKit packages.
也就是說,這 repo 不是把你鎖進一個大框架裡。它比較像把幾個小零件拼起來:bot engine、agent runtime、rich UI、平台 adapter。這種做法我比較舒服,因為你知道每一層在幹嘛,也比較不會被抽象層搞到頭痛。

它把需要的套件講得很明白,像 @copilotkit/bot、@copilotkit/runtime、@copilotkit/bot-ui、@copilotkit/bot-slack。另外還有 Discord、Telegram、WhatsApp 這類 adapter,還有 Redis persistence 這種你真的需要時才會碰的東西。這種「先把邊界講清楚」的文件,我看了就比較不想翻白眼。
我以前被那種「超簡單 starter」整過。文件寫得像一鍵啟動,結果你一進去才發現 bot 層、UI 層、model 層全部焊死。你想換一個工具?先把整個專案摸懂再說。OpenTag 至少把 seam 留給你看,這很難得。
實操寫法:你自己做 agent starter 時,先不要寫 code,先畫四格。第一格是 transport,第二格是 runtime,第三格是 UI,第四格是 storage。任何功能如果塞不進這四格之一,那它多半應該是獨立模組,不該硬塞進核心。
Slack app 設定很煩,所以要把它做得無聊
Create a Slack app. At api.slack.com/apps → From a manifest → paste slack-app-manifest.yaml.
白話翻譯就是:別再手工點 Slack 後台點到懷疑人生了,直接用 manifest。這招很土,但真的省時間。尤其當你只是想先把 bot 跑起來,不想先被 UI 卡死,manifest-driven setup 就是正解。
OpenTag 的 quickstart 也很務實:先建 Slack app、安裝、拿 Bot User OAuth Token 和 App-Level Token,把幾個 secret 塞進 .env,然後把 agent 和 bot 分成兩個 process 跑。我喜歡這種切法,因為它把 mental model 變乾淨了。bot 負責連 Slack,agent 負責推理和工具呼叫,兩邊分開,出問題才知道該怪誰。
我自己做過一個內部 workspace 的支援 assistant,最痛的不是模型,而是 debug。只要 bot 和 runtime 混在同一個 process,任何錯誤都像鬼打牆。分開之後就好多了:Slack 壞了是 Slack 壞,模型壞了是模型壞,工具壞了是工具壞。工程上這種可切分性,比看起來聰明重要太多。
- 用 manifest 建 app,少掉一堆手動設定。
- Token 跟 model key 全部走環境變數,不要寫死。
- bot 和 runtime 分開跑,debug 會少很多怨氣。
實操寫法:就算你不用 OpenTag,也照這個 operational shape 做。manifest、secrets、separate runtime、separate connector。這套不花俏,但不容易炸。
要能動手,但先讓人類按鈕
It renders a breakdown, a table, and a bar chart inline and files a ticket only after an Approve gate.
翻譯一下就是:bot 可以做事,但不能自己當老大。它先把分析結果攤開,必要時還能把表格、圖表直接塞在 Slack 裡,然後在真的要建立 ticket 之前,先等人按批准。這個順序我很在意,因為它把「有用」跟「亂搞」切開了。
很多 agent demo 都很愛秀自動化,結果一上線就出事。某個 noisy incident thread,bot 一口氣幫你開五張 ticket,然後大家開始花時間清理 bot 的錯,而不是處理原本的問題。這種事我看過太多次,所以我現在只相信一件事:凡是會產生 side effect 的動作,都要先過人類。
OpenTag 的好處是它把 approval gate 做成流程的一部分,不是事後補丁。使用者在 Slack 裡就能看到 bot 找到什麼、打算做什麼、會改什麼,然後再決定要不要放行。這比把人導去另一個系統按確認強很多,因為 context 沒有被切斷。
實操寫法:你只要記住一條線。會改狀態、會發訊息、會建立工單、會通知別人的動作,都先擋一下。只讀查詢可以自動做,真正會留下痕跡的動作,先讓人看過再說。這不是保守,這是避免 bot 把你搞成救火隊。
一個 prompt 檔,比五層抽象更有用
The agent's behavior is steered by a single system prompt in runtime.ts — rewrite it and you have a different agent.
也就是說,這套東西不是把行為藏在一堆 policy 和 callback 裡,而是盡量集中在一個地方。你改 prompt,agent 的個性、範圍、邊界就跟著變。這種設計我很愛,因為它讓實驗成本低很多。
repo 也很直接地說,你可以直接複製 app/ 當起點,而 runtime.ts 就是 agent backend,透過 AG-UI 對外服務。這個切法讓你可以先專注在一個 Slack bot 上,把工具、approval、prompt 都跑順,再慢慢加別的平台。這比一開始就想做全通路 agent 實際多了。
我最怕那種行為散在十幾個檔案的專案。寫的人以為自己很有架構,改的人只會想離職。OpenTag 這種「行為集中、邊界清楚」的做法,才真的有人會去迭代。因為你知道要改哪裡,也知道改了會影響什麼。
實操寫法:第一版先把 agent 做得很醜也沒關係,但一定要可改。只有一個 system prompt、只有一個 runtime entrypoint、只有一層 adapter。你如果連「行為在哪裡」都講不清楚,後面只會越做越亂。
可抄的模板
# OpenTag 風格的 Slack agent 模板(可直接改成你的專案骨架)
## 目標
- 讀 Slack thread
- 摘要上下文
- 需要時呼叫工具
- 在 Slack 內回傳富格式結果
- 任何 side effect 先走 human approval
## 專案結構
app/
bot/ # Slack adapter、message routing
runtime/ # LLM runtime、tools、system prompt
ui/ # 富訊息元件、表格、圖表
tools/ # Jira、Linear、Notion、DB 查詢等整合
config/ # env 讀取與驗證
scripts/
start-bot.sh
start-runtime.sh
.env.example
README.md
## 環境變數
SLACK_BOT_TOKEN=xoxb-your-bot-token
SLACK_APP_TOKEN=xapp-your-app-token
OPENAI_API_KEY=your-model-key
# 或 ANTHROPIC_API_KEY=your-model-key
REDIS_URL=redis://localhost:6379
## System prompt
你是一個 Slack workspace agent。
目標:
1. 仔細讀 thread。
2. 摘要相關上下文。
3. 只有在有價值時才呼叫工具。
4. 在 Slack 內用結構化方式回報結果。
5. 任何 side effect 都要先得到明確的人類批准。
規則:
- thread 不清楚時先問澄清問題。
- 回答要短、可執行。
- 提議動作時,先講清楚為什麼需要它。
- 工具失敗時要直接回報,不要硬掰。
- 在批准前,不要建立 ticket、不要送訊息、不要改資料。
## Approval gate
在任何 side effect 前,先渲染:
- 你找到什麼
- 你打算做什麼
- 會改變什麼
- 明確的 Approve / Reject 按鈕
## 工具範例
{
"name": "create_ticket",
"description": "建立 issue tracker ticket",
"input_schema": {
"type": "object",
"properties": {
"title": { "type": "string" },
"description": { "type": "string" },
"priority": { "type": "string" }
},
"required": ["title", "description"]
}
}
## Runtime entrypoint
export async function main() {
const bot = createBot({
adapters: [createSlackAdapter({ token: process.env.SLACK_BOT_TOKEN })],
runtime: createRuntime({
model: process.env.OPENAI_API_KEY ? "openai" : "anthropic",
systemPrompt: SYSTEM_PROMPT,
tools: [createTicketTool(), createLookupTool()],
approvalRequiredFor: ["create_ticket", "send_message", "update_record"],
}),
persistence: process.env.REDIS_URL ? createRedisStore(process.env.REDIS_URL) : createMemoryStore(),
});
await bot.start();
}
## 我會怎麼用
1. 先把這段貼進新 repo。
2. 先接一個 Slack adapter。
3. 先做一個 read-only tool。
4. 再做一個需要 approval 的 side-effect tool。
5. prompt 保持可編輯。
6. Slack 跑通後,再考慮加其他平台。
## 開工檢查清單
- [ ] 用 manifest 建 Slack app
- [ ] bot 和 runtime 分開 process
- [ ] 一個可改的 system prompt
- [ ] side effect 前先做人類批准
- [ ] Redis persistence 視需要再加
- [ ] 工具邊界清楚
- [ ] 富格式輸出可讀
## 最小啟動命令
pnpm install
pnpm --filter slack-example runtime
pnpm --filter slack-example dev這份模板我會直接拿去改,原因很單純:它把最難的幾件事都攤在檯面上了。你要換模型、要加 Discord、要改 approval policy,都不需要把整個系統拆掉重來。這才是能在團隊裡活下來的 starter。
OpenTag 是 CopilotKit 的原始作品,我上面這篇是照 repo 結構跟 setup 文件拆出來的讀法,不是官方文案。原始來源在 https://github.com/CopilotKit/OpenTag,setup 細節可看 setup.md。如果你想看它背後用到的組件,也可以對照 CopilotKit、Slack app 文件,以及 OpenAI docs 或 Anthropic docs。