字节笔记本
2026年6月24日
OpenTag:Claude Tag 的开源实现,把 Agent 提及做成协议
OpenTag 是 Claude Tag 工作流的开源实现。它不是又一个 AI 工作区,而是一层协议:让你在 GitHub、Slack 等已经在用的工作区里 @一个 agent,由批准的 runner 调起 Claude Code、Codex 或自定义 agent 在本地干活,再把结果以 PR、评论或审计事件的形式安静地送回原线程。截至撰稿,项目在 GitHub 收获 33 stars,使用 TypeScript 编写,遵循 MIT 协议,当前版本 v0.1.0。
项目简介
OpenTag 由 amplifthq 开发维护,是对 Anthropic Claude Tag 模式的开放实现。Claude Tag 展示了团队 AI 的一种新界面:在工作正在发生的地方 @一个 agent,让它调用合适的工具,结果回到同一条线程里。但 Claude Tag 首发是 Claude 优先、Slack 优先,且仅向 Claude Enterprise / Team 的 beta 用户开放。
OpenTag 面向的是想要同一种交互模式、同时保留开放控制权的开发者和团队。需要强调的是,OpenTag 不隶属于 Anthropic,它是 Claude Tag 让人看清的那种「agent 提及」工作流的开源版本。
为什么需要 OpenTag
OpenTag 把「把 agent 标记进工作」做成一层协议,而不是一个封闭的产品面。它和 Claude Tag 的差异可以用一张表说清:
| Claude Tag 模式 | OpenTag 方式 |
|---|---|
| 在 Slack 里 @Claude | 从 GitHub、Slack 或其他适配器 @任意已配置的 agent |
| Claude 用配置好的工具执行 | 任意批准的 executor 都能跑:Claude Code、Codex、Hermes、OpenClaw 或自定义 |
| Agent 身份由管理员配置 | 仓库与频道绑定是显式、可审计的记录 |
| 工作发生在 Anthropic 的产品边界内 | 调度可自托管、可嵌入、可指向本地 runner |
| 结果回到线程 | 结果可以是评论、进度更新、审计事件、分支或 PR |
核心特性
- 统一事件归一化:GitHub 和 Slack 适配器把 issue 评论、PR review 评论、Slack app 提及都翻译成同一套
OpenTagEventschema,其他工作区界面也可以按同一协议接入。 - 本地优先执行:
opentagd只认领被显式绑定的仓库,并在你的本地 checkout 里执行,不把代码外送。 - 内置 executor:
echo用于冒烟测试、claude-code对应claude --print、codex对应codex exec,开箱即用。 - 安静回调:Slack 的过程进度默认只进审计流,GitHub 则是把一次 run 的所有更新原地 patch 到同一条评论里,避免淹没原线程。
- 协议级工件:支持建议变更(SuggestedChangesSnapshot)、审批决策(ApprovalDecision)、应用计划(ApplyPlan)、策略规则与变更映射、以及 run/repo/work-thread 三级指标。
技术栈
- TypeScript(93.7%)、Node.js 22.x、pnpm 9.x
- Hono — 嵌入式 dispatcher 框架(
@opentag/dispatcher) - Zod — schema 定义与校验、JSON Schema 导出(
@opentag/core) - SQLite + Drizzle — runs、审计事件、提案、审批等持久化(
@opentag/store) - Vitest — 测试框架,内置协议级冒烟测试
工作流程
OpenTag 的核心循环是这样的:
- Ingress 归一化:GitHub/Slack 适配器把评论或 app 提及翻译成统一的
OpenTagEvent。 - Dispatcher 校验作用域:run 必须带仓库元数据,且该仓库必须被显式绑定到某个 runner。
- 本地守护认领:
opentagd检查本地仓库配置后才执行 executor。 - Executor 干活:
echo验证链路;Claude Code 和 Codex 会创建隔离的opentag/<runId>分支,跑本地 CLI。 - 协议工件落地:结果可能包含建议变更、下一步动作提示、提案谱系、审批决策、应用计划及其每意图结果。
- 回调闭环:人类线程收到安静的 ack/final 回调,详细进度和指标则留存在 dispatcher 里可查。
冒烟测试已经验证了两条端到端链路:
- GitHub issue → OpenTag → 本地 Claude Code → 提交分支 → Pull Request → GitHub 回调
- Slack 线程 → OpenTag → 本地 Claude Code → Slack 最终回调(过程进度仅审计)
安装与快速开始
前置要求:Node 22.x 和 pnpm 9.x。
安装已发布的包族:
pnpm add @opentag/core @opentag/client @opentag/dispatcher @opentag/github @opentag/slack @opentag/runner @opentag/store或直接从仓库构建:
pnpm install
pnpm test
pnpm smoke:protocol
pnpm smoke:slack-protocol
pnpm build其中 pnpm smoke:protocol 和 pnpm smoke:slack-protocol 是无密钥的协议冒烟测试,会启动一个进程内的 dispatcher(临时 SQLite),通过 client SDK 跑通整条协议链。完整的本地 GitHub→runner 冒烟测试可参照 examples/github-to-echo:启动 dispatcher、绑定本地 runner、构造一个 GitHub 形态的 run、用 echo executor 执行,然后翻看审计日志。
本地运行 Claude Code
通过 opentag.local.json 配置一个使用内置 Claude Code executor 的仓库绑定:
{
"runnerId": "runner_local",
"dispatcherUrl": "http://localhost:3031",
"pairingToken": "dev_pairing_token",
"repositories": [
{
"provider": "github",
"owner": "acme",
"repo": "demo",
"checkoutPath": "/Users/example/repos/demo",
"defaultExecutor": "claude-code",
"baseBranch": "main",
"pushRemote": "origin"
}
]
}然后注册、绑定并启动守护进程:
OPENTAG_CONFIG_PATH=opentag.local.json pnpm --filter @opentag/opentagd dev -- register-runner
OPENTAG_CONFIG_PATH=opentag.local.json pnpm --filter @opentag/opentagd dev -- bind-repos
OPENTAG_CONFIG_PATH=opentag.local.json pnpm --filter @opentag/opentagd dev -- serve真实的本地冒烟测试可以用仓库提供的脚本:
scripts/dev/run-gh-claude-local-test.sh
scripts/dev/run-slack-claude-local-test.sh两者都使用真实的平台回调,同时把执行留在本地。
试用本地 echo 回环
想最小成本验证整条链路,可以跑 echo executor。先启动 dispatcher:
OPENTAG_DATABASE_PATH=opentag.db pnpm --filter @opentag/dispatcher-app dev准备 opentag.local.json(把 defaultExecutor 设为 echo)后,注册并绑定本地 runner:
OPENTAG_CONFIG_PATH=opentag.local.json pnpm --filter @opentag/opentagd dev -- register-runner
OPENTAG_CONFIG_PATH=opentag.local.json pnpm --filter @opentag/opentagd dev -- bind-repos创建一次 run 并执行:
curl -X POST http://localhost:3030/v1/runs \
-H 'content-type: application/json' \
-d @examples/github-to-echo/run.example.json
OPENTAG_CONFIG_PATH=opentag.local.json pnpm --filter @opentag/opentagd dev -- run-once查询结果:
curl http://localhost:3030/v1/runs/run_demo_1
curl http://localhost:3030/v1/runs/run_demo_1/events包与应用
当前公开发布版本为 v0.1.0,npm 包统一在 @opentag scope 下。
| 包 | 用途 |
|---|---|
@opentag/core | Zod schema、TS 类型、协议辅助、mention 解析、JSON Schema 导出 |
@opentag/client | HTTP 客户端:ingress 应用、本地 runner、admin、提案、审批、apply、策略、映射、指标 |
@opentag/dispatcher | 可嵌入的 Hono dispatcher 与 callback sink |
@opentag/github | GitHub 事件归一化、评论渲染、PR 辅助、issue 变更编译/apply 辅助 |
@opentag/slack | Slack 事件归一化、thread key、callback 辅助 |
@opentag/store | SQLite/Drizzle 持久化:runs、审计事件、提案、审批、apply、策略、映射、租约、指标 |
@opentag/runner | executor 契约,外加 echo、Claude Code、Codex executor 适配器 |
可运行应用:
| 应用 | 用途 |
|---|---|
apps/dispatcher | 托管 dispatcher 进程 |
apps/opentagd | 本地守护进程,认领并执行 runs |
apps/github-probot | GitHub App ingress |
apps/slack-events | Slack Events API ingress |
SDK 使用示例
归一化一条 GitHub 评论并入队:
import { createOpenTagClient } from "@opentag/client";
import { normalizeGitHubIssueComment } from "@opentag/github";
const event = normalizeGitHubIssueComment({
id: String(payload.comment.id),
commentBody: payload.comment.body,
commentUrl: payload.comment.html_url,
apiCommentsUrl: payload.issue.comments_url,
issueUrl: payload.issue.html_url,
issueNumber: payload.issue.number,
owner: payload.repository.owner.login,
repo: payload.repository.name,
actorId: payload.sender.id,
actorLogin: payload.sender.login,
private: payload.repository.private,
receivedAt: new Date().toISOString()
});
if (event) {
const client = createOpenTagClient({
dispatcherUrl: process.env.OPENTAG_DISPATCHER_URL!,
pairingToken: process.env.OPENTAG_DISPATCHER_TOKEN
});
await client.createRun({
runId: `run_${Date.now()}`,
event
});
}把 dispatcher 嵌进另一个 Hono 兼容的服务:
import { createDispatcherApp, createGitHubCallbackSink } from "@opentag/dispatcher";
export const dispatcher = createDispatcherApp({
databasePath: "opentag.db",
pairingToken: process.env.OPENTAG_PAIRING_TOKEN,
callbackSink: createGitHubCallbackSink({
token: process.env.OPENTAG_GITHUB_TOKEN
})
});Executor 模型
OpenTag 把 executor 当成适配器,而不是系统的中心。一个 executor 接收 runId、workspacePath、归一化后的命令文本、以及来自源工作区的上下文指针;返回结论、人类可读的摘要、变更文件、验证结果,以及分支或 PR 等可选工件。
内置的 Codex 和 Claude Code executor 会拒绝脏工作区、创建隔离分支、运行本地 CLI(codex exec 或 claude --print)、过滤内部工件、报告变更文件,并返回结构化的 OpenTagRunResult。当允许创建 PR 时,opentagd 会先提交 executor 产生的文件变更,再推送 run 分支并开 PR。第三方 runner 可以按 @opentag/runner 里的 ExecutorAdapter 契约自行实现。
回调投递
- GitHub:给 dispatcher 设置
OPENTAG_GITHUB_TOKEN后,ack/进度/最终回调会以评论形式发出。每次 run 只维护一条评论,后续进度和最终回调原地 patch 这条评论。若启用 dispatcher 回调,需在 Probot 应用上设OPENTAG_DISPATCHER_OWNS_CALLBACKS=true,避免重复 ack 评论。 - Slack:给 dispatcher 设置
OPENTAG_SLACK_BOT_TOKEN后,ack 和最终回调通过chat.postMessage发到 Slack 线程;常规过程进度默认只进审计流。 - 鉴权:在 dispatcher 上设
OPENTAG_PAIRING_TOKEN即可要求/v1/*端点携带共享 Bearer token,与opentagd配置里的pairingToken同值;通过 dispatcher 创建 run 的 ingress 应用则设OPENTAG_DISPATCHER_TOKEN。
项目状态
OpenTag 还是个年轻的 v0 项目,当前代码库已经跑通核心循环:GitHub 与 Slack ingress、归一化协议 schema、dispatcher 持久化与租约/提案/审批/apply/策略/映射/谱系/指标、本地守护轮询与心跳、echo 与 Claude Code 和 Codex executor、带安静默认值的回调,以及包级 SDK 用法。
接下来要做的方向包括:更完善的托管设置流程、GitHub Project 的状态/优先级字段映射、更多工作区适配器编译器、适配器级的上下文包脱敏与分类钩子,以及面向多租户 dispatcher 的生产加固。架构与产品方向见 docs/design.md,版本与发布规则见 docs/versioning.md。
项目链接
- GitHub 仓库:amplifthq/opentag
- npm scope:
@opentag/* - License:MIT