ByteNoteByteNote

字节笔记本

2026年6月24日

OpenTag:Claude Tag 的开源实现,把 Agent 提及做成协议

API中转
¥120

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 提及都翻译成同一套 OpenTagEvent schema,其他工作区界面也可以按同一协议接入。
  • 本地优先执行opentagd 只认领被显式绑定的仓库,并在你的本地 checkout 里执行,不把代码外送。
  • 内置 executorecho 用于冒烟测试、claude-code 对应 claude --printcodex 对应 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 的核心循环是这样的:

  1. Ingress 归一化:GitHub/Slack 适配器把评论或 app 提及翻译成统一的 OpenTagEvent
  2. Dispatcher 校验作用域:run 必须带仓库元数据,且该仓库必须被显式绑定到某个 runner。
  3. 本地守护认领opentagd 检查本地仓库配置后才执行 executor。
  4. Executor 干活echo 验证链路;Claude Code 和 Codex 会创建隔离的 opentag/<runId> 分支,跑本地 CLI。
  5. 协议工件落地:结果可能包含建议变更、下一步动作提示、提案谱系、审批决策、应用计划及其每意图结果。
  6. 回调闭环:人类线程收到安静的 ack/final 回调,详细进度和指标则留存在 dispatcher 里可查。

冒烟测试已经验证了两条端到端链路:

  • GitHub issue → OpenTag → 本地 Claude Code → 提交分支 → Pull Request → GitHub 回调
  • Slack 线程 → OpenTag → 本地 Claude Code → Slack 最终回调(过程进度仅审计)

安装与快速开始

前置要求:Node 22.x 和 pnpm 9.x。

安装已发布的包族:

bash
pnpm add @opentag/core @opentag/client @opentag/dispatcher @opentag/github @opentag/slack @opentag/runner @opentag/store

或直接从仓库构建:

bash
pnpm install
pnpm test
pnpm smoke:protocol
pnpm smoke:slack-protocol
pnpm build

其中 pnpm smoke:protocolpnpm 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 的仓库绑定:

json
{
  "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"
    }
  ]
}

然后注册、绑定并启动守护进程:

bash
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

真实的本地冒烟测试可以用仓库提供的脚本:

bash
scripts/dev/run-gh-claude-local-test.sh
scripts/dev/run-slack-claude-local-test.sh

两者都使用真实的平台回调,同时把执行留在本地。

试用本地 echo 回环

想最小成本验证整条链路,可以跑 echo executor。先启动 dispatcher:

bash
OPENTAG_DATABASE_PATH=opentag.db pnpm --filter @opentag/dispatcher-app dev

准备 opentag.local.json(把 defaultExecutor 设为 echo)后,注册并绑定本地 runner:

bash
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 并执行:

bash
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

查询结果:

bash
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/coreZod schema、TS 类型、协议辅助、mention 解析、JSON Schema 导出
@opentag/clientHTTP 客户端:ingress 应用、本地 runner、admin、提案、审批、apply、策略、映射、指标
@opentag/dispatcher可嵌入的 Hono dispatcher 与 callback sink
@opentag/githubGitHub 事件归一化、评论渲染、PR 辅助、issue 变更编译/apply 辅助
@opentag/slackSlack 事件归一化、thread key、callback 辅助
@opentag/storeSQLite/Drizzle 持久化:runs、审计事件、提案、审批、apply、策略、映射、租约、指标
@opentag/runnerexecutor 契约,外加 echo、Claude Code、Codex executor 适配器

可运行应用:

应用用途
apps/dispatcher托管 dispatcher 进程
apps/opentagd本地守护进程,认领并执行 runs
apps/github-probotGitHub App ingress
apps/slack-eventsSlack Events API ingress

SDK 使用示例

归一化一条 GitHub 评论并入队:

typescript
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 兼容的服务:

typescript
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 接收 runIdworkspacePath、归一化后的命令文本、以及来自源工作区的上下文指针;返回结论、人类可读的摘要、变更文件、验证结果,以及分支或 PR 等可选工件。

内置的 Codex 和 Claude Code executor 会拒绝脏工作区、创建隔离分支、运行本地 CLI(codex execclaude --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

项目链接

分享: