Browse by type
An open-source, self-hosted knowledge-work agent for Slack and Microsoft Teams — connected tools and generative UI included.
See it work · Quick start · Make it yours · Channels SDK
https://github.com/user-attachments/assets/46fb9854-7540-4756-a33f-fe97810f80d4
A spreadsheet goes in. A native Slack chart, a Linear issue behind an approval gate, and a cited research brief come back — in the thread, where the work already is.
Channels SDK brings any AG-UI agent into Slack and Microsoft Teams. Its README shows you the pieces. OpenTag is those pieces assembled into something you would actually deploy — and it is built to be taken, not just read.
| Clone it | Customize it | Ship it |
|---|---|---|
| A working knowledge-work agent in one quick start: managed Channel, Node runtime, Python LangGraph agent, connected tools, and native Slack UI. | The agent, the persona, the tools, and the UI are each one file or one directory. Point it at your own agent without touching the Channel lifecycle. | Two Railway services, pinned SDK versions, graceful shutdown, and a live Slack harness — production shape, not demo shape. |
Deployment options:
.railway/railway.ts.Release images publish as ghcr.io/copilotkit/opentag-agent and
ghcr.io/copilotkit/opentag-runtime.
Slack and Microsoft Teams are supported today. Discord, Telegram, and WhatsApp are coming soon.
Every frame below is this repository running in a real Slack workspace.
| Native UI from a file | Approval before a write | Research with sources |
|---|---|---|
![]() |
![]() |
![]() |
A .csv becomes a Slack chart and an insight, not a wall of numbers. |
Linear and Notion writes pause for a human. The button resumes the LangGraph run. | Live web research comes back as a table with the links it used. |
Prerequisites: Node.js 22+, pnpm, Python 3.12,
uv, a CopilotKit account, an OpenAI API key,
and a Slack workspace you can install an app into.
OpenTag's channel can run two ways:
pnpm install
Do this before touching Slack. The Channel is what generates a Slack app manifest already pointed at the right Request URL, so creating the Slack app first means creating the wrong one.
Two ways to do it. Both end in the same place.
npx --yes copilotkit@latest channels setup
That installs a channels-setup skill and copies a one-line prompt to your
clipboard (--no-clipboard prints it instead). Paste it into your agent:
Use the
channels-setupskill to set up a Channel for this project.
The skill is deliberately a pointer rather than a copy of the steps: it fetches its workflow from https://copilotkit.ai/channels-guide.md at run time, so it cannot go stale against the CLI. It covers the whole path — selecting the project, creating and reconciling the Channel, walking you through the Slack console handoff, and proving a real mention gets a reply. It hands every secret back to you; it never asks you to paste one into chat.
It writes .agents/skills/channels-setup and a skills-lock.json, both already
gitignored here. Note that it installs to every coding agent it detects with no
way to narrow the list. To install for one agent only, use the equivalent
command, which takes the same prompt:
npx --yes copilotkit@latest skills onboard --channels --agent claude-code
The skill will also carry you through the rest of this quick start. One thing to
watch: its workflow is written for a project starting from nothing, so it has
phases for building the agent and writing the Channel runtime. OpenTag already
has both — agent/ and server.ts. Point your agent
at the existing code to verify and run, not to rewrite.
npx --yes copilotkit@latest project select
npx --yes copilotkit@latest channels add --name open-tag --display-name "OpenTag" --adapter slack --json
channels add declares the Channel in .copilotkit/channels.json, creates it
on the server, and returns a JSON envelope with one of three states:
completed — the adapter is attached. Continue to step 3.blocked — a normal pause waiting on you in the Slack console. Read
nextAction: it carries the prefilled manifest link, the exact environment
variable names to set, and the resumeCommand to run afterward. This exits 0.failed — stop and read the error code. Do not continue.--name is a slug and must match INTELLIGENCE_CHANNEL_NAME character for
character. If you are running a fork against your own project, pick your own
name — see Channel names claim deliveries.
For Microsoft Teams, use --adapter teams. Two Teams steps stay yours because
nothing can work around them: granting tenant admin consent, and uploading the
app package through Apps → Manage your apps → Upload an app.
These apply on either path. Follow the CLI's emitted nextAction rather than
remembered Slack steps, and watch for:
xoxb-) from OAuth & Permissions, not
the token shown in the app-creation modal, and the Signing Secret from
Basic Information → App Credentials. Those two values are all the Slack
adapter wants — there is no app-level xapp- token anywhere on this path.cp .env.example .env
Set:
OPENAI_API_KEY=sk-...
AGENT_URL=http://localhost:8123/
INTELLIGENCE_API_KEY=cpk-...
INTELLIGENCE_CHANNEL_NAME=open-tag
# Optional: assign OpenTag Threads to an existing Learning Container.
INTELLIGENCE_LEARNING_CONTAINER_ID=support-quality
# Optional: use another user-facing identity, such as Kite.
AGENT_DISPLAY_NAME=OpenTag
Both the Node runtime and the Python agent load this one root .env; Railway
supplies the same values as service variables. Tavily, GitHub, PostHog, Linear,
and Notion are optional — see Optional research
sources.
INTELLIGENCE_API_URL and INTELLIGENCE_GATEWAY_WS_URL already default to the
production Intelligence endpoints in
app/env.ts, so leaving them unset is correct.
INTELLIGENCE_LEARNING_CONTAINER_ID is also optional. When set, it must name
an existing Learning Container in the project selected by
INTELLIGENCE_API_KEY; when omitted, OpenTag does not assign Threads to a
Learning Container.
pnpm dev
The predev hook syncs the locked Python environment and installs Playwright's
Chromium. pnpm dev then runs the Python agent with reload enabled and the Node
runtime in watch mode. The runtime waits for its Intelligence connection to
become ready before its HTTP listener accepts traffic.
/invite @OpenTag
Installed in the workspace is not the same as present in a conversation. Slack
emits no app_mention at all for a channel the app is not a member of, so
without this OpenTag looks broken while behaving correctly.
A Channel that installs cleanly and answers nothing is the most expensive failure available here, because it looks finished. Three checks separate the two. Send them from a real human account:
@OpenTag what changed in the last deploy? — expect a
useful, model-backed reply.If any of those fail, in this order:
npx --yes copilotkit@latest channels status --json
It reports declaration, source, server, adapter, environment, and lifecycle
diagnostics. Resolve every one. Two of its warnings are expected for OpenTag
and are not faults: it flags INTELLIGENCE_API_URL and
INTELLIGENCE_GATEWAY_WS_URL as unset because
app/env.ts defaults them in code rather than in .env.
LOG_LEVEL=debug pnpm runtime
The runtime logger defaults to error, while every Channel lifecycle
breadcrumb is emitted at warn — including channel "<name>" requires setup,
the single highest-value diagnostic here. At the default level it is written and
discarded.
Note that the runtime does not hot-reload Channel wiring. After editing a
handler, the agent, or the Channel, restart the process and confirm online
again before retesting. A stale process answering with the old behavior is
indistinguishable from a change that did not work.
Managed delivery is claim-based: two runtimes declaring the same Channel name in the same Intelligence project race per delivery, and the loser silently receives nothing. The tell is a Slack reply your terminal knows nothing about.
INTELLIGENCE_CHANNEL_NAME defaults to open-tag, which is the name the
production deployment uses. Give a local or forked runtime its own Intelligence
project, its own API key, and its own Channel name.
OpenTag is meant to be forked. Each thing you would want to change is one file or one directory, and none of them require touching the Channel lifecycle.
| To change… | Edit | Notes |
|---|---|---|
| The user-facing name | AGENT_DISPLAY_NAME |
browse all types & interfaces →
$ claude mcp add OpenTag \
-- python -m otcore.mcp_server <graph>