Channel Bot logo

Channel Bot

Organization
vstorm-co
channel-bot

Work with messaging-channel bots — Telegram, Slack, Mattermost or Microsoft Teams. Register a bot, route inbound messages through an agent, choose webhook vs polling, handle an @slug mention, or add a new channel adapter. Use when wiring chat into a messaging platform or debugging bot delivery, identity linking or "the bot answered as itself".

Overview

Publishervstorm-co
Repositoryagenticos
Skill namechannel-bot
Stars
137
Forks
27
Bundled files
Instructions only
LicenseApache-2.0
Links
  • Markdown instructions

    A SKILL.md file the model loads on demand, so it only costs tokens when a request actually matches.

  • Works with any LLM

    AI skills are plain Markdown, not provider-specific code, so this works with GPT, Claude, Gemini, Grok, or a local model.

  • Self-contained

    Everything the model needs lives in the instructions — no extra files to sync.

  • Open source

    Published by vstorm-co on GitHub. Read the source before you install it.

Installation

Install the Channel Bot AI skill in TypingMind to use it with any LLM, or drop it into another agent that reads SKILL.md.

1

Install in TypingMind

TypingMind installs a skill straight from its GitHub folder — it reads SKILL.md, bundles the resource files, and stores the result locally.

  1. Open the app and go to Plugins → Skills.
  2. Choose "Install from GitHub".
  3. Paste the skill folder URL below and confirm.
    https://github.com/vstorm-co/agenticos/tree/main/.claude/skills/channel-bot
  4. Enable the skill in any chat where you want it available.
Plugins → Skills → Add skill → From GitHub URL, then paste the folder URL and press Continue.
2

Install in another agent

Any agent that reads the Agent Skills format can use this skill — copy the folder into that agent's skills directory.

Claude Code — .claude/skills
git clone --depth 1 https://github.com/vstorm-co/agenticos.git /tmp/agenticos
mkdir -p .claude/skills
cp -r /tmp/agenticos/.claude/skills/channel-bot .claude/skills/channel-bot
Restart Claude Code after copying so it picks up the new skill.

Use it in TypingMind

Enable Channel Bot in any TypingMind chat and the model takes it from there. Its name and description sit in the system prompt, and the moment a request matches, the model loads the full instructions itself — you never invoke it by hand, and it costs no tokens until it is actually used.

The model loads Channel Bot on its own as soon as a request matches it.

Works with any AI model

AI skills are plain Markdown instructions rather than provider-specific code, so Channel Bot is not tied to the model it was written for. Install it once in TypingMind and use it with GPT-5, Claude, Gemini, Grok, DeepSeek, Mistral, Llama, or a local model you run yourself — all on your own API keys.

  • Loaded only when it is needed

    The system prompt carries just the name and description. The instructions are fetched on the first matching request, so an idle skill costs nothing.

  • Switch models mid-chat

    Because the skill is instructions rather than code, changing model does not break it — the next model reads the same SKILL.md.

Skill instructions

This is the SKILL.md content the model loads. Read it before installing — a skill is instructions your model will follow.

Channels — Telegram, Slack, Mattermost, Teams

Read docs/channels.md. Code: app/services/channels/ — a thick service with per-platform adapters plus a router that funnels inbound messages into the same agent pipeline as the web chat.

FileResponsibility
base.pyShared adapter interface
telegram.pyTelegram (aiogram v3)
slack.pySlack (Events API + Socket Mode)
mattermost.pyMattermost
teams.py, teams_auth.pyMicrosoft Teams over the Bot Connector's HTTP protocol (#1985): JWT-verified activities, Adaptive Cards, conversation references in teams_conversations
router.pyInbound platform message → conversation/session → agent run → reply
mentions.py@slug resolution, and the identity rules below

Bots live in the DB (channel_bots), with per-user identity (channel_identity) and per-thread session (channel_session).

Surface prompts

formatting.house_style supplies current defaults at run time. Binding prompts store custom additions only; do not seed a snapshot of defaults into a new row. Migration 0183_channel_prompt_defaults.py removes exact historical defaults. Channel messages persist raw text and channel_identity_id; history author labels are resolved just before dispatch by ChannelReference, so erasure removes the labels as well. build_message_history carries opaque identity references; PreparedRun.channel_authors preserves per-user-part attribution across provider normalization and approval resumption. ChannelReferencePart marks reference-only parts omitted from run manifests. Screen editable reference values with the input policy, keeping fixed transport metadata out of question checks. Keep JSON envelopes out of Message.content. Channel metadata remains request-only reference data, outside system instructions and the guarded input. Never infer identity or authorization from display labels.

Tokens go through the vault

Bot tokens are sealed with app/core/vault.py, bound to the organization. CHANNEL_ENCRYPTION_KEY and the deployment-wide Fernet key are gone, removed before the chain was squashed into 0001_baseline. channel_bots.secret_key_version exists because the token is now an envelope and a staged master-key rotation has to know which key sealed it. See the vault-secrets skill.

The mention invariants

These are the ones worth testing and the ones easiest to break:

  • A bot serves exactly one agent - uq_exposure_bot, migration 0018. So answer_default takes the single active binding without asking what a second row would have meant, and @slug is an alias for that agent rather than a router between several. Binding a second agent is refused by the service and by the database.
  • @slug resolves only inside the bot's organization. A slug from another org is not found, not borrowed.
  • A mention runs as the sender, never as the bot. The bot's own identity would carry whatever permissions it was registered with, which is how a channel becomes a privilege-escalation path.
  • An unlinked identity is refused, not run with no role. "No role" is not a safe default; it is an unauthenticated run.

tests/test_channel_mentions.py and tests/test_channel_bot_org_scope.py pin these.

CLI

bash
uv run agenticos cmd channel-add-bot \
    --platform telegram --name "Support" --token <token> --mode jwt_linked
uv run agenticos cmd channel-list-bots [--platform telegram]
uv run agenticos cmd channel-test-message --bot-id <uuid> --chat-id <chat> --text ping
uv run agenticos cmd channel-webhook-register --bot-id <uuid>
uv run agenticos cmd channel-webhook-delete --bot-id <uuid>   # back to polling

There is no cmd channel group — the commands are flat, hyphenated names.

Access modes: open, whitelist, jwt_linked, group_only. jwt_linked refuses an unlinked chat account everywhere on its own; require_link is the switch that makes the other modes refuse in a channel too (#639).

Webhook vs polling

  • Polling (dev) — the adapter long-polls; no public URL needed.
  • Webhook (prod) — the platform POSTs to a route under api/routes/v1/. Verify the signature or secret before processing anything (HMAC for Telegram, signing secret for Slack, the Bot Connector JWT for Teams - TeamsAuth.verify, which needs the network and so is not verify_webhook_signature).
  • Teams is push only - there is nothing to poll, and a reply needs the conversation's serviceUrl, which the route records on every activity.

Adding an adapter

  1. Implement services/channels/<platform>.py against base.py: parse inbound into the normalized message, send outbound. Anything per bot the connection needs before it opens - a server address, an app token - is registered in prepare_connection, the base hook the supervisor calls on every adapter; do not add a differently named method for it.
  2. Wire it into router.py so inbound reaches the agent and replies stream back. Do not fork the agent pipeline — one runner behind every surface is the whole design.
  3. Add a signature-verified webhook route and/or a polling entrypoint. A polling entrypoint runs its session under supervise_stream from base.py — the one reconnect loop — and raises ChannelNotConfigured for anything a retry cannot fix (a missing token or URL, a token the platform rejects); do not hand-roll a while True with its own sleep.
  4. Reuse the existing conversation/session model.
  5. Respect the per-group/per-thread concurrency controls already in the adapters.

Where charts come from

Two halves, and the split is the point. The charts capability (app/agents/capabilities/charts/) owns the spec: create_chart returns a ChartSpec, which the web chat renders with Recharts and a replay draws again. A chat platform can render neither, so services/channels/chart_png.py owns the channel raster: mentions.drawn_chart takes the turn's last chart call and render_chart_png rasterises it to a PNG the adapter attaches. RENDERERS there is one entry per ChartType, held equal to the capability's list by tests/test_channel_charts.py — a new type is added in both places or fails there. There is no chart_render.py; the file is chart_png.py.

Coverage

app/services/channels/mentions.py is in the gated platform layer at 100%. The adapters are template-inherited and are not. See the backend-tests skill.

Frequently asked questions

What does the Channel Bot AI skill do?

Work with messaging-channel bots — Telegram, Slack, Mattermost or Microsoft Teams. Register a bot, route inbound messages through an agent, choose webhook vs polling, handle an @slug mention, or add a new channel adapter. Use when wiring chat into a messaging platform or debugging bot delivery, identity linking or "the bot answered as itself".

Why use Channel Bot on TypingMind?

Because you install it once and use it with any model. Channel Bot is plain Markdown rather than provider-specific code, so the same skill runs on GPT-5, Claude, Gemini, Grok, or a local model — and you can switch model mid-chat without it breaking. TypingMind runs on your own API keys, so you pay providers directly instead of a per-seat subscription, and your skills and chats stay in your own storage.

How do I install Channel Bot in TypingMind?

Open Plugins → Skills → Install from GitHub in TypingMind and paste https://github.com/vstorm-co/agenticos/tree/main/.claude/skills/channel-bot. TypingMind reads its SKILL.md and installs it as a skill you can enable per chat.

Which AI models can use Channel Bot?

Any model you connect in TypingMind. AI skills are plain Markdown instructions rather than provider-specific code, so GPT, Claude, Gemini, Grok, and local models can all load this skill when a request matches it.

How many AI models can I use with Channel Bot?

As many as you like. As long as a model supports skills, you can use Channel Bot with it — GPT, Claude, Gemini, Grok, DeepSeek, Mistral, Llama and more — all on TypingMind with your own API keys.

Is the Channel Bot AI skill free?

Yes. It is published on GitHub by vstorm-co under the Apache-2.0 license. You only pay your own AI provider for the tokens you use.

What are AI skills?

An AI skill is a reusable instruction bundle that teaches an AI model how to do one specific task. It follows the open Agent Skills format: a SKILL.md file with a name and description, plus any scripts, templates or reference files the model may need. The model reads the instructions only when your request matches the skill, so an installed skill costs nothing until it is used.

How are AI skills different from plugins or MCP servers?

A plugin or MCP server gives a model new tools to call — code that runs somewhere and returns a result. An AI skill gives the model knowledge and process instead: how to approach a task, which steps to follow, what good output looks like. Skills are plain Markdown, so they need no server, no API key and no runtime, and they work with any model.

View all

Set up your own AI workspace now

Get notified about new features and future giveaways by subscribing to our newsletter 👇