Skip to content

feat: per-chat routing, group prefix trigger, and conversation history#165

Open
morellid wants to merge 3 commits intoRichardAtCT:mainfrom
morellid:feat/per-chat-routing-group-prefix
Open

feat: per-chat routing, group prefix trigger, and conversation history#165
morellid wants to merge 3 commits intoRichardAtCT:mainfrom
morellid:feat/per-chat-routing-group-prefix

Conversation

@morellid
Copy link
Copy Markdown

Summary

  • Per-chat working directories: new PERSONAL_CHAT_ID/PERSONAL_CHAT_DIRECTORY and GROUP_CHAT_ID/GROUP_CHAT_DIRECTORY settings map specific Telegram chat IDs to dedicated Claude sessions and working directories. Unrecognised chats fall back to APPROVED_DIRECTORY.
  • Session isolation per chat: all session state (claude_session_id, force_new_session, etc.) moved from context.user_data (per-user) to context.chat_data (per-chat), so each chat gets an independent Claude session even for the same user.
  • Group trigger prefix + conversation history: in group/supergroup chats Claude is silent by default. Only messages starting with GROUP_TRIGGER_PREFIX (default: claude) — or the slash form /claude — produce a response. All other messages are stored in a rolling buffer (last 30 messages); when Claude is triggered the most recent 20 are prepended as context.

New files

  • src/bot/features/chat_routing.pyget_working_directory() routing helper and GroupChatBuffer rolling history buffer

Changed files

  • src/config/settings.py — 5 new optional fields + validators
  • src/bot/orchestrator.py — uses chat_data for session state; group prefix filter in agentic_text, agentic_document, and _handle_agentic_media_message
  • .env.example — documents the new settings

Test plan

  • DM the bot → Claude uses PERSONAL_CHAT_DIRECTORY, session is independent from group
  • Send a plain message in the group → no response, message stored in buffer
  • Send claude <request> or /claude <request> in the group → Claude responds with recent conversation as context
  • /new in DM resets only the DM session, group session unaffected
  • No routing config set → behaviour identical to before (falls back to APPROVED_DIRECTORY, no group filtering)

Adds three connected features for multi-chat deployments:

**Per-chat working directories**
New PERSONAL_CHAT_ID / PERSONAL_CHAT_DIRECTORY and GROUP_CHAT_ID /
GROUP_CHAT_DIRECTORY settings map specific Telegram chat IDs to dedicated
Claude sessions and working directories. Unrecognised chats fall back to
APPROVED_DIRECTORY.

**Session isolation per chat**
All session state now lives in PTB's context.chat_data (keyed by chat ID)
instead of context.user_data (keyed by user ID), giving each chat its own
independent Claude session even when the same user writes in multiple chats.

**Group trigger prefix + conversation history**
In group/supergroup chats Claude is silent by default. Only messages
starting with GROUP_TRIGGER_PREFIX (default: "claude") — or its slash form
(/claude) — produce a response. All other messages are stored in a rolling
buffer (last 30 messages). When Claude is triggered, the most recent 20
buffered messages are prepended as context so Claude can follow the
conversation without everyone having to address the bot directly.
@RichardAtCT
Copy link
Copy Markdown
Owner

Good concept — per-chat routing and group silencing are genuinely useful features. The implementation is mostly clean but there are a couple of blockers and several things worth addressing before merge.


🔴 Blockers

1. No type hints in chat_routing.py

Violates the project's mypy strict + disallow_untyped_defs=true requirement. Every function needs full annotations:

def append(buffer: list[dict[str, str]], sender_name: str, text: str) -> None: ...
def format_history(messages: list[dict[str, str]]) -> str: ...
def get_working_directory(chat_id: int, settings: Settings) -> str: ...

2. No automated tests

The group trigger logic has meaningful branching (exact match, prefix match, slash variant, stripping, history injection) and get_working_directory has three paths. All of these are pure functions — trivially testable with pytest. Manual-only is not acceptable for a feature this size.


🟡 Non-blocking issues

3. Redundant slice guard

# Current
context_messages = history[-HISTORY_CONTEXT_SIZE:] if len(history) > HISTORY_CONTEXT_SIZE else history
# Simplified — slice handles the short-list case natively
context_messages = history[-HISTORY_CONTEXT_SIZE:]

4. getattr fallback should be direct access

group_trigger_prefix is now a defined settings field — use self.settings.group_trigger_prefix directly instead of getattr(..., "claude").

5. Telegram slash commands with @botname suffix

In groups, Telegram often appends the bot username: /claude@mybotname. The current slash prefix logic won't match this form. Worth handling:

slash_variants = (slash_prefix + " ", slash_prefix + "@")

6. chat_data buffer persistence is implicit

Buffer is stored in chat_data which only persists across restarts if a Persistence object is configured. Worth a note in .env.example or a startup warning when no persistence is configured.

7. Prefer telegram constants

from telegram import Chat
if chat_type in (Chat.GROUP, Chat.SUPERGROUP):

8. GroupChatBuffer is a namespace, not a class

A class with only @staticmethod methods and no instance state is just a module with functions in a trench coat. Either give it real state (own the buffer) or make them module-level functions.


✅ What's good

  • Moving session state from user_datachat_data is the right call for group isolation
  • buffer[:-1] to exclude the triggering message from history context is a nice touch
  • Blank-string → None validators in settings are solid defensive config handling
  • Directory existence validation at startup catches misconfiguration early

Summary: Fix the type hints and add at least basic unit tests for the pure functions in chat_routing.py and the trigger logic. The @botname slash issue is worth a fix too — it'll bite immediately in any real group deployment.

Friday, AI assistant to @RichardAtCT (posted as @RichardAtCT — FridayOpenClawBot access pending)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants