homelab/.planning/phases/01-session-process-foundation/01-03-PLAN.md
Mikkel Georgsen 0baaeb26b5 docs(01): create phase plan
Phase 01: Session & Process Foundation
- 3 plan(s) in 2 wave(s)
- 2 parallel (wave 1), 1 sequential (wave 2)
- Ready for execution

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-04 14:32:10 +00:00

12 KiB

phase plan type wave depends_on files_modified autonomous must_haves
01-session-process-foundation 03 execute 2
01-01
01-02
telegram/bot.py
false
truths artifacts key_links
User sends /new myproject in Telegram and receives confirmation that session was created
User sends /new myproject brainstorm and session is created with brainstorm persona
User sends /session myproject in Telegram and active session switches
User sends plain text with no active session and gets prompted to create one
User sends plain text with active session and message is routed to ClaudeSubprocess
/new with duplicate name returns friendly error, not crash
No zombie processes after switching sessions
path provides contains
telegram/bot.py Bot with /new and /session commands wired to session manager and subprocess SessionManager
from to via pattern
telegram/bot.py telegram/session_manager.py SessionManager import and method calls from telegram.session_manager import|SessionManager
from to via pattern
telegram/bot.py telegram/claude_subprocess.py ClaudeSubprocess import for process spawning from telegram.claude_subprocess import|ClaudeSubprocess
from to via pattern
telegram/bot.py /new handler SessionManager.create_session direct method call create_session
from to via pattern
telegram/bot.py /session handler SessionManager.switch_session direct method call switch_session
Wire the session manager and Claude subprocess modules into the existing Telegram bot, adding `/new` and `/session` commands and routing plain text messages to the active session's Claude Code subprocess.

Purpose: This plan connects the foundation pieces (Plans 01 and 02) to the existing Telegram bot. After this plan, users can create sessions, switch between them, and send messages that spawn Claude Code subprocesses. This completes Phase 1's core goal: path-based sessions with subprocess management.

Output: Updated telegram/bot.py with new command handlers and session-aware message routing.

<execution_context> @/home/mikkel/.claude/get-shit-done/workflows/execute-plan.md @/home/mikkel/.claude/get-shit-done/templates/summary.md </execution_context>

@.planning/PROJECT.md @.planning/ROADMAP.md @.planning/STATE.md @.planning/phases/01-session-process-foundation/01-CONTEXT.md @.planning/phases/01-session-process-foundation/01-RESEARCH.md @.planning/phases/01-session-process-foundation/01-01-SUMMARY.md @.planning/phases/01-session-process-foundation/01-02-SUMMARY.md @telegram/bot.py @telegram/session_manager.py @telegram/claude_subprocess.py Task 1: Add /new and /session commands to bot.py telegram/bot.py Modify the existing `telegram/bot.py` to add session management commands and wire up the Claude subprocess for message handling.

New imports to add:

from telegram.session_manager import SessionManager
from telegram.claude_subprocess import ClaudeSubprocess

Module-level state:

  • Create a SessionManager instance (singleton for the bot process)
  • Create a dict subprocesses: dict[str, ClaudeSubprocess] to track active subprocess per session
  • Track active_session: str | None at module level (or via SessionManager)

New command: /new <name> [persona]

Handler: async def new_session(update, context)

  • Extract name from context.args[0] (required)
  • Extract persona from context.args[1] if provided (optional)
  • Validate: if no args, reply with usage: "Usage: /new [persona]"
  • Call session_manager.create_session(name, persona=persona)
  • On ValueError (duplicate name): reply "Session '' already exists. Use /session to switch to it."
  • On success:
    • Auto-switch to new session: call session_manager.switch_session(name)
    • Reply "Session '' created." (include persona name if specified: "Session '' created with persona ''.")
    • Do NOT auto-spawn subprocess yet (spawn happens on first message, per CONTEXT.md: "Switching to a session with no running process auto-spawns Claude Code immediately" -- but creating is not switching. Actually, /new does auto-switch, so it should auto-spawn. However, we can defer spawn to first message for simplicity. Let Claude handle this: spawn subprocess immediately after creation since we auto-switch.)
    • Spawn a ClaudeSubprocess for the new session (but don't send a message yet -- it's just ready)

New command: /session <name>

Handler: async def switch_session_cmd(update, context)

  • Extract name from context.args[0] (required)
  • Validate: if no args, reply with usage and list available sessions
  • If session doesn't exist: reply "Session '' doesn't exist. Use /new to create it."
  • Call session_manager.switch_session(name)
  • If session has no running subprocess: spawn one (create ClaudeSubprocess, don't send message)
  • Reply "Switched to session ''." (include persona if set)

Modified message handler: handle_message

Replace the current implementation (which saves to inbox) with session-aware routing:

  • If no active session: reply "No active session. Use /new to start one."
  • If active session exists:
    • Get or create ClaudeSubprocess for session
    • Call await subprocess.send_message(update.message.text)
    • The on_output callback will need to send messages back to the chat. For Phase 1, create callbacks that send to the Telegram chat:
      async def on_output(text):
          await update.message.reply_text(text)
      
    • Note: For Phase 1, basic output routing is needed. Full Telegram integration (typing indicators, message batching, code block handling) comes in Phase 2.
    • Register handler with block=False for non-blocking execution (per research: prevents blocking Telegram event loop)

Subprocess callback factory:

Create a helper function that generates callbacks bound to a specific chat_id and bot instance:

def make_callbacks(bot, chat_id):
    async def on_output(text):
        # Truncate to Telegram limit
        if len(text) > 4000:
            text = text[:4000] + "\n... (truncated)"
        await bot.send_message(chat_id=chat_id, text=text)

    async def on_error(error):
        await bot.send_message(chat_id=chat_id, text=f"Error: {error}")

    async def on_complete():
        pass  # Phase 2 will add typing indicator cleanup

    async def on_status(status):
        await bot.send_message(chat_id=chat_id, text=f"[{status}]")

    return on_output, on_error, on_complete, on_status

Important: callback async handling. The ClaudeSubprocess callbacks may be called from a background task. Since bot.send_message is async, the callbacks need to be async and properly awaited. The subprocess module should use asyncio.ensure_future() or check if callbacks are coroutines.

Actually, reconsider: the subprocess stream reader runs in a background asyncio.Task. Callbacks called from there ARE in the event loop already. So async callbacks work naturally. The subprocess module should await callback(text) if the callback is a coroutine, or call asyncio.create_task(callback(text)) to not block the reader.

Handler registration updates:

Add to main():

app.add_handler(CommandHandler("new", new_session))
app.add_handler(CommandHandler("session", switch_session_cmd))

Update the text message handler to use block=False:

app.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, handle_message, block=False))

Update /help text: Add /new and /session to the help output.

Keep all existing commands working. Do not remove or modify /status, /pbs, /backups, etc. Only modify handle_message and add new handlers.

Authorization: All new handlers must check is_authorized(update.effective_user.id) as the first line, following existing pattern.

DO NOT:

  • Remove or break existing command handlers
  • Implement typing indicators (Phase 2)
  • Implement message batching or code block handling (Phase 2)
  • Implement idle timeout (Phase 3)
  • Make the bot.py import path different from existing pattern
cd ~/homelab
source ~/venv/bin/activate

# Verify bot.py loads without import errors
python3 -c "
import sys
sys.path.insert(0, '.')
# Just verify imports work - don't run the bot
from telegram.session_manager import SessionManager
from telegram.claude_subprocess import ClaudeSubprocess
print('Imports successful')
"

# Verify new handlers are registered in the code
python3 -c "
source = open('telegram/bot.py').read()
assert 'CommandHandler(\"new\"' in source or 'CommandHandler(\"new\",' in source, 'Missing /new handler registration'
assert 'CommandHandler(\"session\"' in source or 'CommandHandler(\"session\",' in source, 'Missing /session handler registration'
assert 'SessionManager' in source, 'Missing SessionManager usage'
assert 'ClaudeSubprocess' in source, 'Missing ClaudeSubprocess usage'
assert 'block=False' in source, 'Missing block=False for non-blocking handler'
assert 'is_authorized' in source, 'Authorization checks must be present'
print('Handler registration and imports verified!')
"

# Verify existing handlers still present
python3 -c "
source = open('telegram/bot.py').read()
existing = ['status', 'pbs', 'backups', 'beszel', 'kuma', 'ping', 'help']
for cmd in existing:
    assert f'CommandHandler(\"{cmd}\"' in source or f'CommandHandler(\"{cmd}\",' in source, f'Existing /{cmd} handler missing!'
print('All existing handlers preserved!')
"
/new and /session commands are registered in bot.py, SessionManager and ClaudeSubprocess are wired in, plain text messages route to active session's subprocess (or prompt if no session), all existing bot commands still work, handlers use block=False for non-blocking execution. Task 2: Verify session commands work in Telegram Session management commands (/new, /session) integrated into the Telegram bot, with Claude Code subprocess spawning and basic message routing. The bot should handle session creation, switching, and message forwarding to Claude Code. 1. Restart the Telegram bot service: ``` systemctl --user restart telegram-bot.service systemctl --user status telegram-bot.service ``` 2. In Telegram, send `/new test-session` -- expect confirmation "Session 'test-session' created." 3. Send a plain text message like "Hello, what can you do?" -- expect Claude's response back in Telegram 4. Send `/new second brainstorm` -- expect "Session 'second' created with persona 'brainstorm'." 5. Send `/session test-session` -- expect "Switched to session 'test-session'." 6. Send another message -- should go to test-session's Claude (different context from second) 7. Check for zombie processes: `ps aux | grep defunct` -- should be empty 8. Check session directories exist: `ls ~/telegram/sessions/` -- should show test-session and second 9. Send `/new test-session` (duplicate) -- expect friendly error, not crash 10. Send a message without active session (if possible to test): should get "No active session" prompt Type "approved" if sessions work correctly, or describe any issues found. 1. Bot starts without errors after code changes 2. `/new ` creates session directory with metadata.json and persona.json 3. `/session ` switches active session 4. Plain text messages route to active session's Claude Code subprocess 5. Claude Code responses come back to Telegram chat 6. All existing commands (/status, /pbs, /backups, etc.) still work 7. No zombie processes after session switches 8. Duplicate /new returns friendly error 9. Message with no active session prompts user

<success_criteria>

  • /new test creates session and confirms in Telegram
  • /session test switches session and confirms
  • Plain text messages trigger Claude Code and response appears in Telegram
  • Existing bot commands remain functional
  • No zombie processes (verified via ps aux | grep defunct)
  • Bot service runs stably under systemd </success_criteria>
After completion, create `.planning/phases/01-session-process-foundation/01-03-SUMMARY.md`