你团队里用 Claude in Slack 吗?每月 $25/人、对话不能持久化、MCP 工具不支持、想改行为还要写 Support Ticket?CopilotKit 刚开源的 OpenTag,就是冲这个痛点的——它让你在自己的基础设施上跑一个和 Claude in Slack 一模一样的 AI Agent,还能接自己的模型、挂自己的 MCP 工具、渲染图表和卡片。
更让我兴奋的是它的架构设计。OpenTag 不是又一个「把 LLM 接入 Slack」的玩具,它背后有一套清晰的四层协议栈和一个可替换的双运行时设计,和之前拆解过的 Nanobot 和 Pi Agent 走的是完全不同的技术路线。这篇文章从源码出发,拆解 OpenTag 的每一层。
700+ Star 的 Slack Agent 开源替代
先给个全景感:OpenTag 是 CopilotKit 基于其 @copilotkit/channels SDK 构建的参考实现,MIT 协议开源,GitHub 上不到两周就攒了 700+ Star。
它的定位非常明确——一个可以自托管的 Claude in Slack 替代品:
- 在 Slack 频道里 @mention 机器人,它就能读对话、回答问题、调用 MCP 工具
- 生成的响应不是纯文本,而是原生 UI 组件(Slack Block Kit、Discord Components V2、Telegram HTML)
- 所有写操作(创建 Linear Issue、写 Notion 文档)都经过人工确认门(Human-in-the-Loop)
- 支持四种聊天平台:Slack、Discord、Telegram、WhatsApp——同一套代码,一个
createBot启动全部
听起来和 Nanobot 有点像?确实,都是「把 Agent 放进聊天工具」的路子。但 OpenTag 在架构上走了完全不同的方向——它用了一套叫 AG-UI 的协议把 Bot 层和 Agent 层解耦,而不是像 Nanobot 那样用 asyncio.Queue 内部耦合。
四层架构:从聊天界面到 Agent 大脑
OpenTag 的架构可以拆成四层,每层职责单一、通过协议通信:
用户聊天界面 (Slack/Discord/Telegram/WhatsApp)
│
▼ @mention /command
┌─────────────────────────────┐
│ Bot 层 (app/) │ ← createBot({ adapters, tools, components })
│ 平台适配器 · 工具转发 │
│ UI 组件渲染 · HITL 门 │
│ Read Thread · Slash 命令 │
└──────────┬──────────────────┘
│ AG-UI 协议 (POST /api/copilotkit/agent/triage/run)
▼
┌─────────────────────────────┐
│ Agent 运行时 │ ← 可替换:runtime.ts 或 agent/
│ BuiltInAgent (TS) │
│ 或 Deep Agent (Python) │
│ LLM + MCP 工具 │
└──────────┬──────────────────┘
│ MCP (Streamable HTTP)
▼
┌─────────────────────────────┐
│ 数据层 │
│ Linear MCP · Notion MCP │
│ Web Search (OpenAI) │
└─────────────────────────────┘
这个分层和 Pi Agent 的「Provider → Steering → Follow-up」三层有点像,但 OpenTag 用的是一个公开协议 (AG-UI) 来连接 Bot 和 Agent,而不是内部函数调用。这意味着你可以把 Bot 层换成任意客户端,只要它实现了 AG-UI 协议。
AG-UI 协议:Bot 和 Agent 之间的「通用语」
AG-UI 是 CopilotKit 定义的一套 Agent-to-UI 协议,本质上是一个 HTTP SSE 流。Bot 层把用户消息、上下文、前端工具声明打包成 AG-UI 请求发给 Agent,Agent 返回一个事件流——LLM 的 tokens、工具调用请求、UI 组件渲染指令都在这个流里传输。
在 runtime.ts 里,Agent 端只有几十行代码:
const runtime = new CopilotSseRuntime({
agents: { triage: agent },
});
const listener = createCopilotNodeListener({ runtime, basePath: "/api/copilotkit" });
createServer(listener).listen(8200);
Bot 端用 AGENT_URL 指向这个地址,两边就通了。这意味着你有一个 Agent 运行在 GPU 集群上,Bot 运行在 Slack 旁边——只要网络可达,它们就能通信。这和 Grok Build 的 ACP 协议(参考四大协议详解)思路一致:把 Agent 的「大脑」和「嘴巴」拆开,通过标准协议连接。
双运行时设计:简单场景 vs 深度研究
OpenTag 最让我感兴趣的设计决策是提供了两个完全不同的 Agent 后端:
① TypeScript BuiltInAgent (runtime.ts)
一个单 LLM 调用 + MCP 工具的轻量运行时,适合大多数日常对话场景。核心代码不到 100 行 TypeScript:
const agent = new BuiltInAgent({
type: "tanstack",
factory: async (ctx) => {
return chat({
adapter: openaiText(model),
messages,
systemPrompts: [SYSTEM_PROMPT],
tools: [webSearchTool({ type: "web_search" }), ...clientTools],
...(mcpClients.length > 0 ? { mcp: { clients: mcpClients } } : {}),
});
},
});
它用 TanStack AI 的 chat() 做多轮工具循环,MCP 客户端在每次 turn 开始时创建、结束时关闭——临时连接模式,避免长时间持有 MCP 连接。
② Python Deep Agent (agent/)
一个基于 LangGraph + Deep Agents 的深度研究 Agent,用了 write_todos 做任务规划、虚拟文件系统做中间持久化、可选的 Tavily 做网络搜索。它不是简单的一轮 LLM 调用,而是:
- 用
write_todos制定研究计划 - 逐个执行研究步骤(通过
research()工具调用内部子 Agent) - 把发现写入虚拟文件系统
- 合成最终报告
切换:改 .env 里的 AGENT_URL 就行——http://localhost:8200 用 TS,http://localhost:8123 用 Python。Bot 层完全不知道背后换了个大脑。
这比 Nanobot 的「单 Agent Loop」更灵活。Nanobot 的 TurnState 只有一套 8 状态机(RESTORE→COMPACT→COMMAND→BUILD→RUN→SAVE→RESPOND→DONE),而 OpenTag 让你在不同 Agent 实现之间切换,甚至可以在生产环境用 TS 运行时做日常对话、用 Python Deep Agent 做深度研究,通过路由层分发。
跨平台 UI 渲染:JSX 编译成原生组件
这是 OpenTag 最亮眼的技术点。@copilotkit/channels-ui 定义了一套跨平台 JSX 组件,编译时自动映射到各平台的原生 UI:
| JSX 组件 | Slack | Discord | Telegram |
|---|---|---|---|
<Table> |
Block Kit Section | Embed | HTML <table> |
<IssueCard> |
Block Kit Section | Embed | Markdown |
<Button> |
Button Element | Button Component | HTML <button> |
<Modal> |
Modal View | Modal | 降级为对话流 |
这个设计的巧妙之处在于:Agent 只需要调用 render_chart、render_table、issue_card 等工具,Bot 层负责把工具调用翻译成对应平台的 UI 渲染。Agent 不关心用户用的是 Slack 还是 Telegram。
对比来看,Nanobot 的 15+ 平台支持也是类似的思路(Channel 接口抽象),但 Nanobot 的输出是纯文本 + Markdown,没有原生 UI 组件渲染。OpenTag 的 JSX 编译方案更接近「一次编写,到处原生渲染」的理想。
MCP 集成与容错设计
OpenTag 的 MCP 集成有两个值得关注的设计:
1. 独立的 MCP 连接超时容错
在 runtime.ts 里,每个 MCP 服务器独立连接,失败不影响其他服务器:
const settled = await Promise.allSettled(
transports.map((t) => connectMcp(t.transport)),
);
失败的 MCP 服务器被记录到 unavailable[],系统提示词自动追加一条「数据源状态」说明,告诉模型哪些源当前不可用。模型只在用户明确需要那个源时才告知不可用——不会自己主动说,避免打断对话。
2. 热度超时 + 泄漏防护
connectMcp() 有一个 8 秒超时机制。如果超时先到达,它会确保后续返回的 MCP 客户端被立即关闭:
connecting.then(
(client) => { if (timedOut) void client.close().catch(() => {}); },
() => {}, // 延迟拒绝不能 crash 进程
);
这是个教科书级的异步容错模式——我觉得 MCPZERO 的 MCP 连接池也可以借鉴这个思路。
Human-in-the-Loop:写操作必须先确认
所有写操作(创建 Linear Issue、写 Notion 文档)都经过一个 confirm_write 阻塞门。Agent 在写操作前必须调用 confirm_write 工具,等待用户在聊天界面点击 Create 或 Cancel。
这个门在 app/human-in-the-loop/confirm-write.tsx 里实现为阻塞 UI 组件——Agent 无法继续,直到用户做出决定。不像某些 Agent 框架的「先写再确认」,OpenTag 是「确认了才能写」,避免了误操作。
三方横向对比
OpenTag、Nanobot 和 Pi Agent 都算是「让 Agent 接入聊天工具」的方向,但技术路线差异很大:
| 维度 | OpenTag | Nanobot | Pi Agent |
|---|---|---|---|
| 语言 | TypeScript (+ Python 可选) | Python 3.11+ | TypeScript |
| 核心协议 | AG-UI (HTTP SSE) | asyncio.Queue (内存) | 无标准化协议 |
| Agent 运行时 | CopilotKit BuiltInAgent / LangGraph Deep Agent | AgentLoop + AgentRunner | Steering + Follow-up 双队列 |
| 多平台 | Slack/Discord/Telegram/WhatsApp | 15+ 聊天平台 | 30+ Provider (非聊天平台) |
| UI 渲染 | JSX → 原生组件编译 | 纯文本/Markdown | 纯文本/Markdown |
| 工具系统 | MCP (Streamable HTTP) | Python Tool 类 | Plugin 系统 |
| 人工确认 | 阻塞式 confirm_write | 无内置 HITL | 无内置 HITL |
| 模型 | OpenAI-only (当前) | 多 Provider 抽象 | 30+ Provider 抽象 |
| 部署 | Railway one-click | 传统部署 | 传统部署 |
| 记忆系统 | 无(对话级) | Dream 两阶段记忆合并 | JSONL 树形分支会话 |
核心差异点:OpenTag 走的是协议驱动架构(AG-UI 解耦 Bot 和 Agent),Nanobot 走的是事件驱动架构(MessageBus 解耦模块),Pi Agent 走的是队列驱动架构(Steering + Follow-up 流水线)。三个方案都能把 Agent 放进聊天工具,但弹性和复杂度取舍不同。
安全隐患
OpenTag 的自托管模式要求 Bot 进程持有平台 Token(Slack Bot Token、Discord Bot Token 等),这意味着如果 Bot 进程被攻破,攻击者可以用这个 Token 冒充机器人在聊天工具里做任何事。
CopilotKit 对此的解决方案是 Intelligence Gateway 模式:Bot 进程不持有任何平台 Token,Token 由 CopilotKit Intelligence 平台保管,Bot 通过 WebSocket 连接到 Intelligence 的 Realtime Gateway。但这个模式目前还是 CopilotKit 的托管服务,不是开源方案——自托管的用户只能用 self-hosted 模式,把 Token 暴露在进程环境变量里。
另外,OpenTag 的 MCP 连接使用了 Bearer Token 认证(Authorization: Bearer),但通信没有端到端加密。如果 MCP 服务器和 Bot 进程之间的网络被嗅探,Token 和数据都可能泄露。这对 Notion MCP 侧车尤其关键——Notion 的 Integration Token 对所有 workspace 内容有读写权限。
相比之下,Nanobot 的 15+ 平台支持同样面临 Token 管理问题,但 Nanobot 的 Token 是每个 channel 实例化的,影响范围更小。Pi Agent 不直接处理聊天平台 Token,风险面更窄。
写在最后
OpenTag 的出现让我看到 Agent 聊天工具这个赛道正在快速成熟。CopilotKit 用 @copilotkit/channels SDK 把「把 Agent 放进聊天工具」这件事从手动工程变成了框架级能力,而 AG-UI 协议的设计让 Bot 和 Agent 可以独立演化——这是正确的方向。
当然,OpenTag 还远不是 Claude in Slack 的平替。它目前只支持 OpenAI 模型,没有持久化记忆,没有多租户隔离,安全模型也还在早期。但作为一个架构参考实现,它的设计思路——AG-UI 协议、双运行时、跨平台 JSX 渲染、MCP 容错——值得每一个做 Agent 基础设施的人认真看看。
GitHub: https://github.com/CopilotKit/OpenTag
延伸阅读: