OpenTag: a Slack teammate that queues coding-agent work on your own machine
Mention any ACP coding agent from Slack, GitHub, GitLab, Linear, or Lark. OpenTag runs Claude Code, Codex, Cursor and more on your own machine, then replies in-thread with verified, evidence-backed results.
At a glance
- What is it?
- OpenTag pairs a self-hosted Control Plane with one Runner so that Slack mentions become ACP coding-agent runs against a local checkout, with results reported back in the same thread. It is deliberately narrow: one Runner, one agent, Slack only.
- Who is it for?
- Adopt OpenTag if you have a public HTTPS origin, Docker Compose and PostgreSQL, and you want Slack mentions to become ACP coding-agent runs on a machine you control, with draft PR publication kept as a separate governed stage.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 2 days ago.
- What is it written in?
- Mainly TypeScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 15, 2026, and from our analysis. They are not legal advice.
DEEP OPEN-SOURCE ANALYSIS
The gap OpenTag fills between a chat request and a coding agent
Most coding-agent setups assume the person who wants work done is sitting at the machine that can do it. OpenTag separates those two things. A Slack mention queues the request; a paired Runner executes it later, against the local checkout, through the Agent Client Protocol. The README frames the product as a persistent teammate rather than a dashboard: the self-hosted relay receives Slack requests and can explain that the Runner is offline instead of disappearing with the laptop. That sentence describes the actual failure mode being solved. If the only surface for an agent is a terminal on someone's laptop, requests die when the lid closes, and nobody in the channel knows whether work was dropped or is still running. OpenTag keeps the request visible and queued. The intended audience is an engineering team that already lives in Slack, already has coding agents installed and authenticated on developer machines, and wants those agents reachable from a shared channel without moving source code or credentials to a hosted service. The README is explicit about the data boundary: source code, local checkout and worktrees, coding-agent login and session, and the GitHub credential stay on the Runner; Slack credentials and the Control Plane's service credentials stay on the Control Plane. The Control Plane keeps coordination truth, not a copy of the development machine.
Control Plane, Runner, and the facts each one owns
The architecture is a straight line with one branch. Slack is the only Source App in the supported team profile. Requests go to the self-hosted Control Plane, which persists to PostgreSQL and hands work to one paired Runner. The Runner invokes the configured ACP coding agent, which operates on the local checkout, and the Runner can publish a GitHub draft PR plus readback. Status flows back to Slack in the originating thread. The README is careful about ownership: the Control Plane owns the canonical Work, Attempt, lease, approval, Effect evidence, channel projection, and terminal assessment. One paired Runner is the execution owner. GitHub is a Project Target plus an optional publication and evidence provider, not a second request inbox. That distinction matters because it rules out a common design where an issue comment and a chat message are both treated as triggers. Here they are not. The evidence model is also split by design. Executor output, Run state, GitHub publication and provider delivery remain separate facts with separate evidence. A provider timeout or ambiguous side effect stays outcome_unknown, and the README states OpenTag does not invent a success or blindly replay it. For anyone who has watched an agent framework report success after a failed push, that last constraint is the most interesting thing in the repository. It is a refusal to collapse distinct facts into one green check.
Teammate states are projections, not commands
The console exposes each active Slack binding as a long-lived Teammate. Its work state is derived from facts that already exist: Project Target, fresh Runner readiness, and the binding's current Work. The README says this adds no Teammate table, no second lifecycle and no mutable presence state. There are six states. ready means the Project Target, Runner and fresh readiness all exist. queued means a request is durably waiting for the paired Runner. working means the current fenced Attempt is assigned or running on a ready Runner. needs_attention means a decision, reconciliation, or conflicting active work needs a human. runner_offline means the Teammate stays in Slack but its Runner has no fresh readiness receipt. setup_required means the Slack binding, Project Target or Runner is incomplete. The important line is that these states are projections, not commands: they cannot claim, retry, cancel or settle work. That is a deliberate constraint on the UI layer, and it is the kind of boundary that keeps a status display from quietly becoming a second scheduler. The cost is that the console will not let you nudge a stuck run from the state view. Recovery happens through the pairing and work machinery, not through a button that mutates lifecycle.
Getting the Control Plane and the Runner running
The presence side needs Docker Compose, PostgreSQL through the included Compose profile, and a public HTTPS origin that Slack can reach. The README gives this sequence: clone the repository, change into deploy/compose, copy .env.example to .env, then run docker compose --env-file .env up --build. Before that last command you replace every placeholder in .env and create the file-backed Slack and relay-content secrets described in the Compose guide. The Runner side installs a CLI globally with npm install -g @opentag/cli@0.11.0, then runs opentag setup --relay https://relay.example.com followed by opentag start. Setup configures and pairs one Runner, one GitHub Project Target and one ACP executor. Two constraints in the README deserve attention. First, the paired-only runtime requires the trusted self-hosted Control Plane URL and a bootstrap pairing credential, and there is no standalone local mode. Second, when prompted you must enter the exact OPENTAG_SLACK_PROJECT_TARGET_ID used by the active Slack binding; setup registers it through the Runner credential and verifies the Control Plane readback before pairing completes. If the machine previously ran a pre-reset OpenTag checkout, the README instructs pointing OPENTAG_CONFIG_HOME and OPENTAG_STATE_DIR at new empty directories, because the paired Runner does not reinterpret or rewrite an earlier config or SQLite database. opentag pair exists only to finish an interrupted pairing or pair an existing unpaired configuration. Verification before first real use is opentag doctor and opentag status.
What the repository does not claim, and the cost of that narrowness
The README lists what the current product path excludes: managed hosting, high availability, other Source Apps, GitHub webhook ingress, multi-Runner scheduling, ambient memory, automatic merge, and a general software-factory planner. That list is unusually honest and it also defines the limits. One Runner means one execution owner, so there is no scheduling layer to spread load or fail over. Slack only means a team that coordinates in Linear, GitLab or Lark cannot use the supported path even though the project description mentions those surfaces. No GitHub webhook ingress means GitHub is a Project Target and publication provider, not a trigger. No automatic merge means draft PR publication is a distinct, governed stage, and chat text cannot broaden access. The practical failure mode is the one the states already name: runner_offline. When the paired machine has no fresh readiness receipt, the Teammate stays in Slack and requests queue durably, but nothing executes until that Runner returns. If your team expects an agent to be available around the clock regardless of any single machine, this design will not deliver that, and the README says so. The second limitation is operational: the Control Plane is self-hosted, so you own the PostgreSQL instance, the public HTTPS origin and the file-backed secrets. There is no hosted fallback to escape to when your origin is down.
Where OpenTag sits next to a hosted agent runner
The natural comparison is a hosted coding-agent service that connects to your repository and runs work in the provider's cloud. The difference is not features, it is where execution and credentials live. A hosted runner typically takes a repository credential, clones the code into provider infrastructure, and executes there; the provider owns availability and you trade away control of the checkout. OpenTag inverts that. Execution happens on the paired, user-controlled computer with its approved local Project Target, and the coding-agent login and session never leave that machine. The Control Plane holds coordination state, not the code. The trade is straightforward: you keep the checkout, the agent session and the GitHub credential on hardware you control, and in exchange you accept that the agent's availability is bounded by that hardware's readiness. A hosted service does not have a runner_offline state because it does not depend on your laptop. OpenTag does, and it surfaces that state in Slack rather than hiding it. For teams with source-control or data-residency constraints, that inversion is the reason to look at this project at all. For teams that want an agent that answers at 3am without anyone's machine being awake, a hosted runner is the better fit and OpenTag is the wrong tool.
Licence, maintenance, and what to verify before trusting it
OpenTag is MIT licensed, which permits commercial use, modification and redistribution provided the licence and copyright notice are preserved. That is the whole of the licence implication here; anything beyond it depends on your own legal review, and nothing in the repository changes the fact that you are running a self-hosted Control Plane that stores Work, Attempt, approval and Effect evidence in your PostgreSQL. Maintenance signals visible in the supplied material: the default branch is main, the repository is not archived, the last push is dated 2026-09-10, and the recent release list shows v0.10.0 on 2026-08-17, v0.9.0 on 2026-07-28 and v0.8.0 on 2026-07-27. The README pins the CLI at @opentag/cli@0.11.0 while the newest listed release is v0.10.0, so check which CLI version matches the Control Plane you deploy rather than assuming they move together. Upgrade cost is not documented in the supplied material: there is no migration guide, and the only version-related instruction is the pre-reset warning about pointing OPENTAG_CONFIG_HOME and OPENTAG_STATE_DIR at new empty directories because the paired Runner does not reinterpret an earlier config or SQLite database. Treat that as the boundary to test first. The repository's own verification commands are corepack pnpm install --frozen-lockfile, corepack pnpm typecheck, corepack pnpm test and corepack pnpm build, and the README states plainly that these prove local source behavior only, not a deployed relay, a live Slack delivery, a GitHub publication, or installation-level availability. So the first thing to verify is not the test suite. It is a real end-to-end request: pair a Runner, run opentag doctor and opentag status, mention the Teammate in Slack, and confirm the thread reports executor output, Run state and GitHub publication as separate facts.
Editorial conclusion
Adopt OpenTag if you have a public HTTPS origin, Docker Compose and PostgreSQL, and you want Slack mentions to become ACP coding-agent runs on a machine you control, with draft PR publication kept as a separate governed stage. Do not adopt it if you need managed hosting, multi-Runner scheduling, GitHub webhook ingress, or any Source App other than Slack; the README states there is no local_direct mode and the supported product always pairs a Runner with the self-hosted Control Plane. Before the first real request, run opentag doctor and opentag status, and confirm the OPENTAG_SLACK_PROJECT_TARGET_ID you enter matches the active Slack binding, because setup verifies that readback before pairing completes.
Community notes