# OpenTag: a Slack teammate that runs coding agents on your own machine

> OpenTag pairs a self-hosted Control Plane with one Runner so Slack mentions queue work for Claude Code, Codex or Cursor against a local checkout. The design is deliberately narrow, and the README says so.

**amplifthq/opentag** — 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.

- Repository: https://github.com/amplifthq/opentag
- Stars: 1,361 · Forks: 77
- Language: TypeScript
- License: MIT
- Published: 2026-09-10 · Updated: 2026-09-10 · Language: en
- Canonical page: https://hysenlabs.com/projects/amplifthq-opentag

## The problem OpenTag picks, and who it is for

Most coding agents live and die with the laptop they run on. Shut the lid and the request vanishes; the channel has no way to say what happened. OpenTag's answer is a self-hosted Control Plane that stays reachable and a paired Runner that owns execution. The README frames the product as "Your persistent AI teammate in Slack, running on a computer you control." The teammate remains visible in Slack even when its computer is offline, and a request queued during that window stays visibly queued rather than silently dropped.

The target user is an engineering team that already has a local checkout and a coding-agent login it is not willing to hand to a hosted service. OpenTag's split is explicit: source code, worktrees, the coding-agent session and the GitHub credential stay on the Runner; Slack credentials and the Control Plane's service credentials stay on the Control Plane. That is a coordination layer, not a copy of a development machine.

It is a poor fit for anyone who wants a hosted bot with no infrastructure. The README states there is no managed hosting claim, no high availability, and no `local_direct` compatibility mode. The supported path always pairs a Runner with the self-hosted Control Plane.

## How a Slack mention becomes a governed attempt

The data flow in the README is a straight line: Slack channel to self-hosted Control Plane, Control Plane to PostgreSQL and to the paired Runner, Runner to the ACP coding agent, agent to the local checkout, Runner to a GitHub draft PR with readback, and back to Slack. Slack is the only Source App in the supported team profile. GitHub appears twice, as a Project Target and as an optional publication and evidence provider, and the README is careful to say it is not a second request inbox.

Ownership is split rather than shared. One paired Runner is the execution owner. The Control Plane owns the canonical Work, Attempt, lease, approval, Effect evidence, channel projection and terminal assessment. That vocabulary matters because it is where the honesty of the tool sits. Executor output, Run state, GitHub publication and provider delivery are kept as separate facts with separate evidence, so a successful agent run does not by itself imply a successful PR publication.

The failure handling is the part worth reading twice. A provider timeout or an ambiguous side effect stays `outcome_unknown`; OpenTag does not invent a success or blindly replay it. Publication of a draft PR is a distinct, governed stage, and the README states plainly that OpenTag does not auto-merge or let chat text broaden access. The console shows each active Slack binding as a long-lived Teammate whose state is derived from facts that already exist, with no extra Teammate table or mutable presence state.

## Teammate work states are projections, not commands

The six states in the README are `ready`, `queued`, `working`, `needs_attention`, `runner_offline` and `setup_required`. They read like status labels, and that is exactly what they are. The README says these states are projections and that they cannot claim, retry, cancel or settle work. A channel that shows `runner_offline` is describing the absence of a fresh readiness receipt, not offering a button to restart anything.

That constraint is a design decision with a cost. You get a state you can trust because it is derived from Project Target, fresh Runner readiness and the binding's current Work rather than stored separately. You also get no remote control surface. If a Runner is wedged, the Slack thread will tell you it is offline; recovery happens on the machine, not in the channel.

The `needs_attention` state is the one that will show up in practice. The README describes it as a decision, reconciliation or conflicting active work that needs a human. Because the Control Plane holds leases and approvals, a conflicting active work item surfaces as a human problem instead of two agents editing the same checkout.

## Installing the Control Plane and pairing a Runner

Two pieces go up. The Control Plane runs from the Compose profile in the repository, and the README lists Docker Compose, PostgreSQL through that included profile, and a public HTTPS origin Slack can reach as prerequisites. Clone the repository and start the stack from `deploy/compose`:

```bash
git clone https://github.com/amplifthq/opentag.git
cd opentag/deploy/compose
cp .env.example .env
docker compose --env-file .env up --build
```

Before that last command, the README says to replace every placeholder in `.env` and create the file-backed Slack and relay-content secrets described in the Compose guide at `deploy/compose/README.md`. Skipping that step is the most likely reason a first start fails.

The Runner side is a global npm install and a two-command start:

```bash
npm install -g @opentag/cli@0.11.0
opentag setup --relay https://relay.example.com
opentag start
```

Setup configures and pairs one Runner, one GitHub Project Target and one ACP executor. When prompted, enter the exact `OPENTAG_SLACK_PROJECT_TARGET_ID` used by the active Slack binding; the README says 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, point `OPENTAG_CONFIG_HOME` and `OPENTAG_STATE_DIR` at new empty directories, because the paired Runner does not reinterpret an earlier config or SQLite database. Use `opentag pair` only to finish an interrupted pairing or to pair an existing unpaired configuration. Then check the installation before the first real request:

```bash
opentag doctor
opentag status
```

After that, a mention in the channel is the interface. The README's example is `@OpenTag investigate the failing check and propose a fix`. If the Runner is offline the request stays visibly queued, and if work needs a decision Slack shows the exact action that needs attention.

## What the narrow scope rules out

The README lists its own exclusions, and they are worth taking literally: no managed hosting, no high availability, no other Source Apps, no GitHub webhook ingress, no multi-Runner scheduling, no ambient memory, no automatic merge, no general software-factory planner. One paired Runner is the execution owner, so the architecture does not scale sideways by adding machines.

A second limitation is verification. The repository's own check commands, `corepack pnpm typecheck`, `corepack pnpm test` and `corepack pnpm build`, prove local source behavior only. The README says so directly: they do not prove a deployed relay, a live Slack delivery, a GitHub publication, or installation-level availability. Anyone treating a green CI run as evidence that their deployment works is reading it wrong.

The third is operational. The Control Plane is a stateful service with PostgreSQL behind it and a public HTTPS origin in front, and the README's own prerequisite list assumes you can provide both. If a hosted Slack bot with no infrastructure is what you actually want, OpenTag is the wrong tool, and the absence of a `local_direct` mode means there is no lighter path inside this project.

## Against a plain self-hosted Slack bot

The obvious alternative is a small self-hosted Slack app that shells out to a coding agent on a server. That approach is simpler to stand up and has fewer moving parts, and for a single operator on a single machine it may be enough. The difference is what happens around the agent invocation.

A shell-out bot typically has one fact: the process exited. OpenTag separates executor output, Run state, GitHub publication and provider delivery into distinct facts with distinct evidence, holds leases and approvals in the Control Plane, and keeps an ambiguous provider result as `outcome_unknown` rather than resolving it in either direction. It also keeps raw tool output out of the team thread, which the README lists as a feature of the ACP integration.

The trade is real. You take on PostgreSQL, a Compose stack, a public HTTPS origin and a pairing flow with credentials on both sides. In exchange you get a queue that survives an offline laptop and a record of what was actually proven. Teams that only need a person to trigger an agent on a box they are already sitting at will find the Control Plane is more machinery than the problem deserves.

## Maintenance, releases and the MIT licence

The repository is not archived and the last push was on 2026-09-10. Releases have been frequent through the summer: v0.8.0 on 2026-07-27, v0.9.0 on 2026-07-28, and v0.10.0 on 2026-08-17. The README's install command pins the CLI at `@opentag/cli@0.11.0`, which is ahead of the most recent release listed in the repository metadata, so check which CLI version the Control Plane you deploy expects before upgrading one side alone.

Upgrade cost is concentrated in the pairing boundary. The README warns that a paired Runner does not reinterpret or rewrite an earlier config or SQLite database, and that a machine which ran a pre-reset checkout should point `OPENTAG_CONFIG_HOME` and `OPENTAG_STATE_DIR` at new empty directories. That is a migration instruction, not a convenience: plan for a fresh state directory when the pairing contract changes, rather than expecting an in-place upgrade.

The project is MIT licensed, which is permissive and places few obligations on how you deploy or redistribute it. That is a statement about the licence text, not advice about your situation; the security posture is documented separately in `SECURITY.md`, and the Compose guide covers the secrets the deployment expects.

## Conclusion

Adopt OpenTag if you already run Claude Code, Codex or another ACP agent locally and want a Slack channel to queue work against a checkout you control, and if you can host the Compose stack plus PostgreSQL behind a public HTTPS origin. Skip it if you need managed hosting, high availability, multiple Runners, GitHub webhook ingress or any source app other than Slack; the README explicitly disclaims all of those. Before trusting it with real work, run opentag doctor and opentag status on the paired machine, then confirm the OPENTAG_SLACK_PROJECT_TARGET_ID you entered matches the active Slack binding, because the README says setup verifies that readback before pairing completes.

## FAQ

### What is OpenTag?

OpenTag is a self-hosted Slack teammate that queues work from a channel and runs an ACP coding agent such as Claude Code, Codex or Cursor on a paired computer you control, then reports status and evidence back in the same thread. The README describes it as running on a computer you control, with source code and agent credentials staying on the Runner.

### Is OpenTag reliable?

The README is explicit about where it refuses to guess: a provider timeout or ambiguous side effect remains outcome_unknown rather than being reported as success or replayed. Reliability still depends on your own deployment, and the repository's check commands prove local source behavior only, not a deployed relay or live Slack delivery.

### Does OpenTag need a server, or can it run locally only?

It needs a server. The README states there is no local_direct compatibility mode and that the supported product always pairs a Runner with the self-hosted Control Plane, which runs from the Compose profile with PostgreSQL and a public HTTPS origin Slack can reach.

### Which coding agents can OpenTag run?

The Runner launches configured coding agents through ACP. The repository description names Claude Code, Codex and Cursor, and the repository includes an ACP integration document at docs/acp-agent-integration.md.

### What does the runner_offline state mean in OpenTag?

It means the Teammate remains in Slack but its Runner has no fresh readiness receipt. The README notes that the request stays visibly queued in that situation, and that these states are projections which cannot claim, retry, cancel or settle work.

## Sources

- [amplifthq/opentag on GitHub](https://github.com/amplifthq/opentag)
- [Issues](https://github.com/amplifthq/opentag/issues)
- [License: MIT](https://github.com/amplifthq/opentag/blob/main/LICENSE)
- [README](https://github.com/amplifthq/opentag/blob/main/README.md)
- [Releases](https://github.com/amplifthq/opentag/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/amplifthq-opentag
