Skip to content

Latest commit

 

History

History
379 lines (300 loc) · 17.8 KB

File metadata and controls

379 lines (300 loc) · 17.8 KB

OpenTag reference

The five-minute path to a working OpenTag is in the README quick start. This file is the reference behind it: components, the full environment contract, Channel commands, optional sources, Railway, and tests.

The canonical deployment is one Python agent service and one Node CopilotRuntime service with Channels embedded. Slack and Microsoft Teams are supported; Discord, Telegram, and WhatsApp are coming soon.

Components

Component Location Responsibility
Runtime entrypoint server.ts Environment, Channels readiness, HTTP lifecycle, and shutdown
Application composition app/index.ts SDK agent factory, managed Channel, and runtime
Channel definition app/channel.tsx Mentions, commands, components, modals, and interrupts
Intelligence runtime app/runtime-host.ts One CopilotKitIntelligence and one CopilotRuntime
Environment contract app/env.ts Required variables and in-code defaults
Python agent agent/ LangGraph deep agent served over AG-UI
Railway topology .railway/railway.ts Two services sourced from OpenTag main
AWS topology deployment/aws/ One private Fargate task, images, secrets, and Datadog log forwarding

The host always uses the Intelligence-owned runtime. It declares one adapter-free Channel using the configured name. The Slack and Microsoft Teams adapters, their credentials, and attachments are configured only in Intelligence — never here.

Install

Prerequisites:

  • Node.js 22+
  • pnpm
  • Python 3.12
  • uv
  • A CopilotKit Intelligence project, Channel, and runtime API key (free plan available) — or an alternative Channels SDK channel runner
  • An OpenAI API key for the Python agent
pnpm install --frozen-lockfile
cd agent
uv sync
cd ..

@copilotkit/channels and @copilotkit/runtime are intentionally pinned. package.json is the single source of truth for both versions; this file does not restate them, because a hand-copied pin drifts on the next bump.

Environment contract

cp .env.example .env

One root .env configures both services. The Python agent loads it explicitly for local development; Railway supplies the same values as service variables without a checked-in file.

Shared identity

Variable Required Purpose
AGENT_DISPLAY_NAME No User-facing identity used by the agent persona and capability UI; defaults to OpenTag

Set the same value on both services when they do not share an environment. For example, AGENT_DISPLAY_NAME=Kite makes the agent introduce itself and render its capability showcase as Kite without renaming the OpenTag project, services, or Channel slug.

Agent

Variable Required Purpose
OPENAI_API_KEY Yes Model access
OPENAI_MODEL No Defaults to gpt-5.5
OPENAI_REASONING_EFFORT No Defaults to low
OPENAI_VERBOSITY No Defaults to low
TAVILY_API_KEY No Enables live web research
GITHUB_PERSONAL_ACCESS_TOKEN No Enables read-only GitHub repository, code, PR, Actions-run, and job-log search. It remains the legacy coding fallback
GITHUB_MCP_URL No Overrides the hosted GitHub MCP URL; OpenTag still sends read-only headers
DAYTONA_API_KEY No Enables the coding subagent (Daytona sandbox)
DAYTONA_SNAPSHOT No Optional Daytona snapshot id. If unset, the first command probes the box. git and pnpm install only when needed. The default snapshot already has Node. pnpm is enabled with Corepack in $HOME/.local/bin
DAYTONA_TTL_MINUTES No Daytona box TTL in minutes. Defaults to 60
GITHUB_CODER_TOKEN No Preferred PAT coding credential. Mutually exclusive with complete GitHub App credentials
GITHUB_APP_ID No GitHub App ID; all three App variables are required together
GITHUB_APP_INSTALLATION_ID No Single supported GitHub App installation ID
GITHUB_APP_PRIVATE_KEY_BASE64 No Base64-encoded GitHub App private-key PEM
POSTHOG_PERSONAL_API_KEY No Enables the hosted PostHog MCP in read-only CLI mode
POSTHOG_MCP_URL No Overrides the hosted PostHog MCP URL
LINEAR_API_KEY No Enables the hosted Linear MCP
LINEAR_MCP_URL No Overrides the hosted Linear MCP URL
NOTION_MCP_AUTH_TOKEN No Bearer token for a remote Notion MCP; requires NOTION_MCP_URL
NOTION_MCP_URL No Remote Notion MCP endpoint; requires NOTION_MCP_AUTH_TOKEN
CORS_ALLOW_ORIGINS No Comma-separated allowed origins; defaults to *
SERVER_HOST No Local bind host; defaults to 0.0.0.0
SERVER_PORT No Local/container port; defaults to 8123
AGENT_RELOAD No Local development reload; disabled by default

To check a live Daytona box (create, echo, git, then delete):

uv run --directory agent python scripts/probe_daytona.py

Only OPENAI_API_KEY is required. Coding stays off until DAYTONA_API_KEY and a PAT or complete GitHub App configuration are set. If both explicit methods are configured, or the App configuration is incomplete, coding stays off and startup logs the configuration problem. GITHUB_ALLOWED_REPOS is no longer enforced; if it remains configured, startup warns that GitHub permissions define access. GitHub MCP stays read-only even when coding is on. Implementation jobs require a scoped brief with files, the exact change, and a test command; repair and merge jobs may inspect the checkout and CI logs to identify those details. Slack does not say "open the PR" unless the user named a PR. If Slack cuts the live update, the job may still be running. Without Tavily or internal-source credentials the agent still chats, triages, and renders supported UI components; planning and virtual files remain available for explicitly substantial work.

Run it alone:

pnpm agent

The AG-UI endpoint is http://localhost:8123/; /health reports the opentag-agent service.

Runtime

Variable Required Purpose
AGENT_URL Yes Python AG-UI endpoint, locally http://localhost:8123/
INTELLIGENCE_API_KEY Yes Runtime authentication; also selects the project
INTELLIGENCE_CHANNEL_NAME No Defaults to open-tag; must match the Channel name exactly
INTELLIGENCE_LEARNING_CONTAINER_ID No Assigns OpenTag Threads to this existing Learning Container
INTELLIGENCE_API_URL No Defaults to https://api.intelligence.copilotkit.ai
INTELLIGENCE_GATEWAY_WS_URL No Defaults to wss://realtime.intelligence.copilotkit.ai
AGENT_AUTH_HEADER No Authorization header forwarded to the agent
PORT No Channel HTTP port; defaults to 3000
LOG_LEVEL No Defaults to error; use debug to see Channel lifecycle breadcrumbs
MERMAID_URL No Overrides the Mermaid browser bundle URL used by diagram rendering

The API key selects a project; the Channel name selects a Channel inside it. When INTELLIGENCE_LEARNING_CONTAINER_ID is set, it must name an existing Learning Container in that same project. Omitting it preserves the default behavior and leaves OpenTag Threads unassigned to Learning. Legacy organization, project, Channel ID, and runtime-instance ID variables are not used. Slack and Teams credentials do not belong here — Intelligence owns them.

Both Intelligence URLs are defaulted in app/env.ts rather than in .env. That is deliberate, and it is why copilotkit channels status reports them as unset. A genuinely missing INTELLIGENCE_GATEWAY_WS_URL does not error: the realtime plane is a different host from the API plane and is not derived from it, so channels.ready() simply hangs until it times out.

Start the runtime:

pnpm runtime

pnpm start and pnpm runtime run the same canonical entrypoint; pnpm dev adds watch mode for both services. Startup waits for listener.channels.ready() before opening HTTP. SIGINT and SIGTERM stop Channels, HTTP, and the rendering browser exactly once, even if shutdown is requested more than once.

Note that ready() resolving is not proof of health. It also resolves on setup_required, which is a valid degraded state rather than a failure. Only controls.status(){ overall, channels } distinguishes them, and /api/copilotkit/info returning 200 reports license and runtime state while saying nothing at all about Slack.

When an agent run fails, Slack gets a short reason (live update cut after about a minute, dropped connection, coder recursion, or the error text). If the user named a GitHub PR, that URL is in the message. Slack does not get a stack trace.

Channel reference

The Channel is created and reconciled with the public CopilotKit CLI. These commands configure managed Intelligence Channels; they do not configure the open-source @copilotkit/channels adapter packages, which are a separate product sharing the words "channels" and "Slack".

Command Purpose
copilotkit project select Select or create the hosted Intelligence project
copilotkit channels add [name] Declare a Channel, reconcile it, and report the next step
copilotkit channels status Compare your configuration, your code, and the server
copilotkit channels list List Channels and their attachment state
copilotkit channels rotate <name> Replace stored provider credentials
copilotkit channels providers List providers and the credentials each asks for
copilotkit channels setup Install the channels-setup skill and hand the flow to your coding agent
copilotkit skills onboard --channels The same prompt, but --agent narrows which agents it installs to

No flag accepts a credential value. Credentials are read from .env, from a named variable via --credential-env <field>=<VAR>, or from a JSON document on stdin via --credentials-stdin for CI and secret managers. --json implies non-interactive: it never prompts and never opens a browser.

channels add writes .copilotkit/channels.json. Keep that file tracked; keep .env and .copilotkit/artifacts/ ignored.

The channels-setup skill installed by channels setup is a pointer, not 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. That workflow assumes a project starting from nothing, so it includes phases for building the agent and writing the Channel runtime — OpenTag has both already. Its Slack handoff never asks anyone to paste a secret into chat.

Credentials each provider asks for

Provider Fields
slack channelToken — Bot User OAuth Token (xoxb-), from OAuth & Permissions; signingSecret, from Basic Information → App Credentials
teams clientId and tenantId, from the Entra app registration Overview; clientSecret — the secret Value, not the Secret ID

There is no app-level xapp- token on the managed path. Slack reaches Intelligence over HTTPS at an Intelligence-hosted Request URL, authenticated by the signing secret Intelligence holds, and Intelligence reaches your runtime over a websocket your process opens outbound. Nothing here uses Socket Mode, and a Slack app configured for Socket Mode installs green and delivers nothing.

copilotkit channels add --adapter teams --provision can create the provider-side Teams app for you. Two Teams gates stay user-owned regardless: granting tenant admin consent, and uploading the app package through Apps → Manage your apps → Upload an app.

Channel names claim deliveries

Managed delivery is claim-based. Two runtimes declaring the same Channel name in the same project race per delivery, and the loser silently receives nothing — the tell is a Slack reply your terminal knows nothing about. Give a local or forked runtime its own project, key, and Channel name rather than reusing open-tag.

The name is a slug: lowercase, digits, single hyphens. It must match INTELLIGENCE_CHANNEL_NAME character for character.

Tools, commands, and UI

OpenTag registers:

  • /agent <text> to run a mention-free prompt.
  • /triage [note] to summarize and propose Linear issues.
  • /preview <title> to preview an issue privately where supported.
  • /file-issue to open a form where supported, with a conversational fallback.

The Channel also forwards sender context, Slack-specific tools on Slack turns, file content, and rich issue/page/table/native-Slack-chart/diagram/status/ incident/link components.

Trigger routing is not symmetric. A mentioned turn goes to onMention if registered and falls back to onMessage otherwise; an unmentioned turn reaches onMessage only. onMention subscribes the thread, which is what lets unmentioned follow-ups in that thread run the agent. Always verify with a channel mention first.

Mentions, messages, and button and select clicks are the proven managed-path triggers — interactivity is enabled deliberately, which is what makes human-in-the-loop fire. Slash commands and modals are registered in code but their managed-path delivery depends on the Channel's generated Slack manifest declaring them. As of the last verification against @copilotkit/channels 0.7.0 the generated manifest declared no slash_commands and view_submission was not handled, so those handlers compiled, started, reported online, and never fired. Send a real command and submit a real modal before relying on either.

Before a Linear or Notion mutation reaches MCP, a Python interceptor emits confirm_write. The Channel posts an approval card, and the button resumes the graph with the user's decision. The MCP handler runs only after approval. Reads and UI rendering are never gated.

Optional sources

Tavily

Set TAVILY_API_KEY to enable live web research. The web_search tool is not registered when the key is absent.

GitHub

Set GITHUB_PERSONAL_ACCESS_TOKEN to enable GitHub search. Use a fine-grained personal access token limited to the repositories and read permissions the agent needs. OpenTag connects to GitHub's hosted MCP with an explicit allowlist of read-only repository, pull-request, Actions-run, and job-log tools. Every loaded tool must advertise readOnlyHint; triggers, reruns, cancels, deletes, and other writes are excluded. Set GITHUB_MCP_URL only to override the hosted endpoint, then restart pnpm agent so it rediscovers the tools.

For coding, prefer a fine-grained GITHUB_CODER_TOKEN; classic PATs continue to work. Alternatively, set all three GitHub App variables. A search PAT may coexist with App coding. The required repository permissions are Contents: read/write, Pull requests: read/write, and Metadata: read-only. Add Actions: read for CI inspection and Workflows: write only when the agent must modify workflow files. Installation-selected repositories are the App authorization boundary. OpenTag does not request or configure branch-protection bypass.

Credentials stay on the OpenTag host. Daytona receives the current token only on clone, pull, and push API calls; the sandbox receives no GitHub environment variable, credential helper, authenticated remote, App JWT, or private key. The coder commits locally, then one confirm_write covers its push and draft-PR create/update. If the push succeeds and the PR write fails, retrying performs only the PR write.

PostHog

Create a PostHog personal API key using the MCP Server preset, then set POSTHOG_PERSONAL_API_KEY. OpenTag connects to https://mcp.posthog.com/mcp in token-efficient CLI mode with server-enforced read-only access. Set POSTHOG_MCP_URL only to override the complete endpoint, including its mode=cli&readonly=true safety parameters. Restart pnpm agent after changing either variable.

Linear

Set LINEAR_API_KEY. OpenTag connects to the hosted Linear MCP by default. Railway preserves this optional secret on the agent service.

Notion

Notion is optional and remote-only, not a separate Railway service. Set both NOTION_MCP_URL and NOTION_MCP_AUTH_TOKEN, then restart pnpm agent so it discovers the tools. If either value is absent OpenTag skips Notion without blocking startup.

Railway

The IaC file declares exactly:

  • agent: CopilotKit/OpenTag, branch main, root agent, Railpack, /health, port 8123.
  • runtime: CopilotKit/OpenTag, branch main, repository root, pnpm runtime, /api/copilotkit/info, port 3000.

runtime.AGENT_URL references the agent's Railway private domain and port. Production Intelligence URLs are literal configuration, the API key is preserved, and the Channel name is open-tag. AGENT_DISPLAY_NAME is preserved independently on both services and must match when overridden. OPENAI_API_KEY is required on agent; Tavily, Daytona/coder, GitHub, PostHog, Linear, and the paired remote Notion variables are optional preserved settings.

Evaluate the configuration locally without applying it:

node node_modules/railway/dist/iac/bin.js

Tests

pnpm install --frozen-lockfile
pnpm check-types
pnpm test
cd agent && uv run pytest

The Slack API live harness is separate from unit tests:

pnpm e2e

See e2e/README.md for its required workspace credentials. There is no launch-blocking Teams E2E harness.

Coming soon

Discord, Telegram, and WhatsApp are intentionally not configured. Their adapters and setup instructions will be added once launch support is ready.