15 KiB
Telegram DM User-Managed Multi-Session Topics Implementation Plan
For Hermes: Use test-driven-development for implementation. Use subagent-driven-development only after this plan is split into small reviewed tasks.
Goal: Add an opt-in Telegram DM multi-session mode where Telegram user-created private-chat topics become independent Hermes session lanes, while the root DM becomes a system lobby.
Architecture: Rely on Telegram's native private-chat topic UI. Users create new topics with the + button; Hermes maps each message_thread_id to a separate session lane. Hermes does not create topics for normal /new flow and does not try to manage topic lifecycle beyond activation/status, root-lobby behavior, and restoring legacy sessions into a user-created topic.
Tech Stack: Hermes gateway, Telegram Bot API 9.4+, python-telegram-bot adapter, SQLite SessionDB / side tables, pytest.
1. Product decisions
Accepted
- PR-quality implementation: migrations, tests, docs, backwards compatibility.
- Use SQLite persistence, not JSON sidecars.
- Live status suffixes in topic titles are out of MVP.
- Topic title sync/editing is out of MVP except future-compatible storage if cheap.
- User creates Telegram topics manually through the Telegram bot interface.
/newdoes not create Telegram topics.- Root/main DM becomes a system lobby after activation.
- Existing Telegram behavior remains unchanged until the feature is activated/enabled.
- Migration of old sessions is supported through
/topiclisting and/topic <session_id>restore inside a user-created topic.
Telegram API assumptions verified from Bot API docs
getMereturns botUserfields:has_topics_enabled: forum/topic mode enabled in private chats.allows_users_to_create_topics: users may create/delete topics in private chats.
createForumTopicworks for private chats with a user, but MVP does not rely on it for normal flow.Message.message_thread_ididentifies a topic in private chats.sendMessagesupportsmessage_thread_idfor private-chat topics.pinChatMessageis allowed in private chats.
2. Target UX
2.1 Activation from root/main DM
User sends:
/topic
Hermes:
- calls Telegram
getMe; - verifies
has_topics_enabledandallows_users_to_create_topics; - enables multi-session topic mode for this Telegram DM user/chat;
- sends an onboarding message;
- pins the onboarding message if configured;
- shows old/unlinked sessions that can be restored into topics.
Suggested onboarding text:
Multi-session mode is enabled.
Create new Hermes chats with the + button in this bot interface. Each Telegram topic is an independent Hermes session, so you can work on different tasks in parallel.
This main chat is reserved for system commands, status, and session management.
To restore an old session:
1. Use /topic here to see unlinked sessions.
2. Create a new topic with the + button.
3. Send /topic <session_id> inside that topic.
2.2 Root/main DM after activation
Root DM is a system lobby.
Allowed/system commands include at least:
/topic/status/sessionsif available/usage/help/platforms
Normal user prompts in root DM do not enter the agent loop. Reply:
This main chat is reserved for system commands.
To chat with Hermes, create a new topic using the + button in this bot interface. Each topic works as an independent Hermes session.
/new in root DM does not create a session/topic. Reply:
To start a new parallel Hermes chat, create a new topic with the + button in this bot interface.
Each topic is an independent Hermes session. Use /new inside a topic only if you want to replace that topic's current session.
2.3 First message in a user-created topic
When a user creates a Telegram topic and sends the first message there:
- Hermes receives a Telegram DM message with
message_thread_id. - Hermes derives the existing thread-aware
session_keyfrom(platform=telegram, chat_type=dm, chat_id, thread_id). - If no binding exists, Hermes creates a fresh Hermes session for this topic lane and persists the binding.
- The message runs through the normal agent loop for that lane.
2.4 /new inside a non-main topic
/new remains supported but replaces the session attached to the current topic lane.
Hermes should warn:
Started a new Hermes session in this topic.
Tip: for parallel work, create a new topic with the + button instead of using /new here. /new replaces the session attached to the current topic.
2.5 /topic in root/main DM after activation
Shows:
- mode enabled/disabled;
- last capability check result;
- whether intro message is pinned if known;
- count of known topic bindings;
- list of old/unlinked sessions.
Example:
Telegram multi-session topics are enabled.
Create new Hermes chats with the + button in this bot interface.
Unlinked previous sessions:
1. 2026-05-01 Research notes — id: abc123
2. 2026-04-30 Deploy debugging — id: def456
3. Untitled session — id: ghi789
To restore one:
1. Create a new topic with the + button.
2. Open that topic.
3. Send /topic <id>
2.6 /topic inside a non-main topic
Without args, show the current topic binding:
This topic is linked to:
Session: Research notes
ID: abc123
Use /new to replace this topic with a fresh session.
For parallel work, create another topic with the + button.
2.7 /topic <session_id> inside a non-main topic
Restore an old/unlinked session into the current user-created topic.
Behavior:
- reject if not in Telegram DM topic;
- verify session belongs to the same Telegram user/chat or is a safe legacy root DM session for this user;
- reject if session is already linked to another active topic in MVP;
SessionStore.switch_session(current_topic_session_key, target_session_id);- upsert binding with
managed_mode = restored; - send two messages into the topic:
- session restored confirmation;
- last Hermes assistant message if available.
Example:
Session restored: Research notes
Last Hermes message:
...
3. Persistence model
Use SQLite, but topic-mode schema changes are explicit opt-in migrations, not automatic startup reconciliation.
Important rollback-safety rule:
- upgrading Hermes and starting the gateway must not create Telegram topic-mode tables or columns;
- old/default Telegram behavior must keep working on the existing
state.db; - the first
/topicactivation path calls an idempotent explicit migration, then enables topic mode for that chat; - if activation fails before the migration is needed, the database remains in the pre-topic-mode shape.
3.1 No eager sessions table mutation for MVP
Do not add chat_id, chat_type, thread_id, or session_key columns to sessions as part of ordinary SessionDB() startup. The existing declarative _reconcile_columns() mechanism would add them eagerly on every process start, which violates the managed-migration requirement.
For MVP, keep origin/session-lane data in topic-specific side tables created only by the explicit /topic migration. Legacy unlinked sessions can be discovered conservatively from existing data (source = telegram, user_id = current Telegram user) plus absence from topic bindings.
If future PRs need richer origin metadata for all gateway sessions, introduce it behind a separate explicit migration/command or a compatibility-reviewed schema bump.
3.2 Explicit /topic migration API
Add an idempotent method such as:
def apply_telegram_topic_migration(self) -> None: ...
It creates only topic-mode side tables/indexes and records:
state_meta.telegram_dm_topic_schema_version = 1
This method is called from /topic activation/status paths before reading or writing topic-mode state. It is not called from generic SessionDB.__init__, gateway startup, CLI startup, or auto-maintenance.
3.3 telegram_dm_topic_mode
Stores per-user/chat activation state. Created only by apply_telegram_topic_migration().
Suggested fields:
chat_idprimary keyuser_idenabledactivated_atupdated_athas_topics_enabledallows_users_to_create_topicscapability_checked_atintro_message_idpinned_message_id
3.4 telegram_dm_topic_bindings
Stores Telegram topic/thread to Hermes session binding. Created only by apply_telegram_topic_migration().
Suggested fields:
chat_idthread_iduser_idsession_keysession_idmanaged_modeautorestorednew_replaced
linked_atupdated_at
Recommended constraints:
- primary key
(chat_id, thread_id); - unique index on
session_idfor MVP to prevent one session linked to multiple topics; - index
(user_id, chat_id)for status/listing.
3.5 Unlinked session semantics
For MVP, a session is unlinked if:
source = telegram;user_id = current Telegram user;- no row in
telegram_dm_topic_bindingshassession_id = session_id.
This is intentionally conservative until a future explicit migration adds richer cross-platform origin metadata.
Never dedupe by title.
4. Config
Suggested config block:
platforms:
telegram:
extra:
multisession_topics:
enabled: false
mode: user_managed_topics
root_chat_behavior: system_lobby
pin_intro_message: true
Notes:
enabled: falsemeans existing Telegram behavior is unchanged.- Activation via
/topicmay create per-chat enabled state only if global config permits it. root_chat_behavior: system_lobbyis the MVP behavior for activated chats.
5. Command behavior summary
/topic root/main DM
- If not activated: capability check, activate, send/pin onboarding, list unlinked sessions.
- If activated: show status and unlinked sessions.
/topic non-main topic
- Show current binding.
/topic <session_id> root/main DM
Reject with instructions:
Create a new topic with the + button, open it, then send /topic <session_id> there to restore this session.
/topic <session_id> non-main topic
Restore that session into this topic if ownership/linking checks pass.
/new root/main DM when activated
Reply with instructions to use the + button. Do not enter agent loop.
/new non-main topic
Create a new session in the current topic lane, persist/update binding, warn that + is preferred for parallel work.
Normal text root/main DM when activated
Reply with system-lobby instruction. Do not enter agent loop.
Normal text non-main topic
Normal Hermes agent flow for that topic's session lane.
6. PR breakdown
PR 1 — Explicit topic-mode schema migration
Goal: Add rollback-safe SQLite support for Telegram topic mode without mutating state.db on ordinary upgrade/startup.
Files likely touched:
hermes_state.py- tests under
tests/
Tests first:
- opening an old/current DB with
SessionDB()does not create topic-mode tables orsessionsorigin columns; - calling
apply_telegram_topic_migration()createstelegram_dm_topic_modeandtelegram_dm_topic_bindingsidempotently; - migration records
state_meta.telegram_dm_topic_schema_version = 1.
PR 2 — Topic mode activation and binding APIs
Goal: Add SQLite persistence for activation and topic bindings.
Tests first:
- enable/check mode row round-trips;
- binding upsert and lookup by
(chat_id, user_id, thread_id); - linked sessions are excluded from unlinked list.
PR 3 — /topic activation/status command
Goal: Implement root activation/status/listing behavior.
Tests first:
/topicin root checksgetMecapabilities and records activation;- capability failure returns readable instructions;
- activated root
/topiclists unlinked sessions.
PR 4 — System lobby behavior
Goal: Prevent root chat from entering agent loop after activation.
Tests first:
- normal text in activated root returns lobby instruction;
/newin activated root returns+button instruction;- non-activated root behavior is unchanged.
PR 5 — Auto-bind user-created topics
Goal: First message in non-main topic creates/uses an independent session lane.
Tests first:
- new topic message creates binding with
auto_created; - repeated topic message reuses same binding/lane;
- two topics in same DM do not share sessions.
PR 6 — Restore legacy sessions into a topic
Goal: Implement /topic <session_id> in non-main topics.
Tests first:
- root
/topic <id>rejects with instructions; - topic
/topic <id>switches current topic lane to target session; - restore rejects sessions from other users/chats;
- restore rejects already-linked sessions;
- restore emits confirmation and last Hermes assistant message.
PR 7 — /new inside topic updates binding
Goal: Keep existing /new semantics but persist topic binding replacement.
Tests first:
/newin topic creates a new session for same topic lane;- binding updates to
managed_mode = new_replaced; - response includes guidance to use
+for parallel work.
PR 8 — Docs and polish
Goal: Document the feature and Telegram setup.
Files likely touched:
website/docs/user-guide/messaging/telegram.md- maybe
website/docs/user-guide/sessions.md
Docs must explain:
- BotFather/Telegram settings for topic mode and user-created topics;
/topicactivation;- root system lobby;
- using
+for new parallel chats; - restoring old sessions with
/topic <id>inside a topic; - limitations.
7. Testing / quality gates
Run targeted tests after each TDD cycle, then broader tests before completion.
Suggested commands after inspection confirms test paths:
python -m pytest tests/test_hermes_state.py -q
python -m pytest tests/gateway/ -q
python -m pytest tests/ -o 'addopts=' -q
Do not ship without verifying disabled-feature backwards compatibility.
8. Definition of done for MVP
/topicactivates/checks Telegram DM multi-session mode.- Root DM becomes a system lobby after activation.
- Onboarding message tells users to create new chats with the Telegram
+button. - Onboarding message can be pinned in private chat.
- User-created topics automatically become independent Hermes session lanes.
/newin root gives instructions, not a new agent run./newin a topic creates a new session in that topic and warns that+is preferred for parallel work./topicin root lists unlinked old sessions./topic <session_id>inside a topic restores that session and sends confirmation + last Hermes assistant message.- Ownership checks prevent restoring other users' sessions.
- Already-linked sessions are not restored into a second topic in MVP.
- Existing Telegram behavior is unchanged when the feature is disabled.
- Tests and docs are included.