개발 블로그
회고 3 분 소요

텔레그램 봇 개발 일지 #3 — 멀티 프로젝트 토픽 라우팅

telegram claude-code supergroup

결론부터

한 봇으로 여러 프로젝트를 다루려면 토픽을 프로젝트 단위로 쓴다. 텔레그램 supergroup에서 forum topics를 켜고, 토픽 이름이 CLAUDE_CWD 아래 폴더명과 일치하면 그 토픽의 메시지는 자동으로 해당 cwd로 라우팅된다. 매칭 안 되는 토픽이나 DM은 글로벌 default cwd를 쓴다. 임시 override는 /cd.

왜 토픽인가

처음엔 "프로젝트마다 봇을 따로 만들까" 고민했다. 그렇게 가면 BotFather에서 N개 토큰, N개 process, N개 화이트리스트, 알림 설정도 N번. 알림이 흩어지고 사이드바가 지저분해진다.

텔레그램의 forum supergroup은 한 그룹 안에서 토픽을 thread처럼 분리한다. 봇은 한 개, 토큰도 한 개, 화이트리스트도 한 벌. 알림은 토픽별로 따로 받을 수 있고, 좌측 사이드바에는 그룹 하나만 떠 있다. 멀티 프로젝트 워크플로우와 잘 맞았다.

토픽 이름 → 폴더 매핑

핵심은 토픽 이름을 cwd로 변환하는 룰이다.

# 매우 단순화된 라우팅 (botlib/routing.py)
def resolve_cwd(message: Message) -> Path:
    base = Path(os.environ["CLAUDE_CWD"])  # 예: D:/playground
    if message.is_topic_message:
        topic_name = topic_names.get(message.message_thread_id)
        candidate = base / topic_name
        if candidate.is_dir():
            return candidate  # 자동 바인딩 성공
    return overrides.get(scope_id) or base

topic_names는 봇이 토픽 생성/이름변경 이벤트를 받아 캐시한 dict다. 토픽 이름 daily-feedD:/playground/daily-feed로 즉시 매핑되므로, 새 토픽을 만든 직후부터 별도 설정 없이 동작한다.

BotFather Group Privacy 봇이 그룹의 일반 메시지를 받으려면 BotFather에서 봇 Group Privacy를 OFF로 켜야 한다. 안 그러면 슬래시 명령(

/cd, /whoami 같은 것)만 보이고 자연어 메시지는 봇에 도달하지 않는다.

/cd 임시 override

토픽 이름이 폴더와 안 맞을 때, 또는 잠깐 다른 디렉토리로 옮겨가야 할 때 /cd <path> 한 줄로 그 (chat, topic) 스코프의 cwd를 갈아끼운다.

/cd D:/work/customer-X
✅ cwd → D:/work/customer-X (이 토픽 한정)

override는 cwds.jsonchat_id:thread_id 키로 영구 저장된다. 봇 재시작 후에도 그대로 살아남고, 토픽별 SDK 세션도 각각 독립이라 다른 토픽에 새는 일이 없다.

토픽=세션 경계 각 토픽이 독립 cwd + 독립 Claude SDK 세션이라는 건, 한 봇 안에서 여러 프로젝트 작업을 동시에 진행하면서 컨텍스트가 섞이지 않는다는 뜻이다. daily-feed 토픽에서 "마지막 커밋?"을 물어도, 옆 토픽인 idea-cycles의 git 상태가 끼어들지 않는다.

다음 편 예고

4편은 hub-and-spoke — 메인 채팅 토픽이 conversational hub가 되어, 프로젝트 작업은 specialist subagent로 dispatch하고 응답만 요약해 받는 패턴을 다룬다.