Architecture Notes By Vincent (June 30th 2026)
- Target initial support for Telegram and Slack.
- Issues: Headless agent lacks full integration initialization; current target path may not be optimal.
- Treat the messaging gateway as a distinct surface area.
- Goal: Fully decouple the gateway from other packages --> if this is true then it means that the gateway is configurable through dependency injection to call other agents.
Key Problem Right Now
- The critical problem however, right now is that we need to be able to spin up an agent and load integrations from it.
OpenSRE Messaging Gateway
Standalone inbound messaging gateway for chat platforms. v1 ships Telegram DM text chat via long polling.
How the pieces fit (surfaces, gateway, integrations)
Three things that are easy to mix up:
- Surface — a way a person talks to the agent (message in, answer out). Today
there are three: the interactive shell (
surfaces/interactive_shell, you type in a terminal), the CLI one-shot (surfaces/cli, one command → one answer), and the gateway (gateway/, you chat with the agent from a chat app). - Gateway — one specific surface: the always-on process that connects a chat app to the agent. Right now it speaks Telegram only.
- Integrations + tools — the outbound side: the agent sending a message out
to a channel.
integrations/telegramandintegrations/slackdeliver messages; the agent calls thetelegram_send_message/slack_send_messagetools to do it.
So the two platforms are not symmetric today:
| Inbound (person → agent) | Outbound (agent → channel) | |
|---|---|---|
| Telegram | Yes — the gateway | Yes — integration + tool |
| Slack | Not yet — surfaces/slack_app is an empty stub |
Yes — integration + tool |
A person can already receive messages the agent sends to Slack, but cannot yet chat to the agent from Slack.
One core for every surface. Shell, CLI, and the Telegram gateway all hand the
message to the same place: a HeadlessAgent (agent.dispatch(message)). They differ
only in how they receive input and send output — never in how the agent thinks.
Quick start
# Allow your Telegram user id (from @userinfobot)
uv run opensre messaging allow -p telegram -u 123456789
# Start the gateway daemon (web app + Telegram chat + task scheduler)
uv run opensre gateway start
DM your bot from Telegram.
Environment variables
| Variable | Purpose |
|---|---|
TELEGRAM_BOT_TOKEN |
Bot token |
TELEGRAM_ALLOWED_USERS |
Comma-separated Telegram user ids |
TELEGRAM_GATEWAY_MAX_CONCURRENT |
Parallel turns across chats (default 4) |
Pairing via opensre messaging pair uses the same integration-store policy as the gateway.
Adding a chat platform (e.g. Slack inbound)
The message handler is already transport-agnostic — it takes
(text, session, sink, logger) and knows nothing about Telegram. So to add Slack
inbound you do not touch the agent, prompts, or tools. You add three small
pieces, the same shape Telegram already has:
- A listener (like
start_telegram_workeringateway/telegram_gateway.py): receives incoming Slack messages (Slack Events API or Socket Mode) and calls the shared handler with(text, session, sink, logger). - An output sink (implement
GatewayOutputSinkfromgateway/gateway_output_sink.py): itsstream()/finalize()send text back to the Slack channel viaintegrations/slack/delivery.py. - A session resolver (like
gateway/storage/session/resolver.py): map a Slack user + channel to aSession.
Then wire it in the composition root (GatewayManager in gateway/manager.py):
start the Slack listener next to (or instead of) Telegram. Reuse the handler from
GatewayTurnHandler(...) as-is.
What you never change: GatewayTurnHandler, Agent, prompts, tools.
Keeping the handler transport-agnostic is exactly what makes a new platform a small,
self-contained add.