homelab/.planning/phases/03-lifecycle-management/03-01-PLAN.md
Mikkel Georgsen 88cd339a54 docs(03): create phase plan for lifecycle management
Phase 03: Lifecycle Management
- 2 plans in 2 waves
- Plan 01 (wave 1): Idle timer module + session metadata + PID tracking
- Plan 02 (wave 2): Suspend/resume wiring, /timeout, /sessions, startup cleanup, graceful shutdown
- Ready for execution

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

6.2 KiB

phase plan type wave depends_on files_modified autonomous must_haves
03-lifecycle-management 01 execute 1
telegram/idle_timer.py
telegram/session_manager.py
telegram/claude_subprocess.py
true
truths artifacts key_links
Per-session idle timer fires callback after configurable timeout seconds
Timer resets on activity (cancel + restart)
Session metadata includes idle_timeout field (default 600s)
ClaudeSubprocess exposes its PID for metadata tracking
path provides min_lines
telegram/idle_timer.py SessionIdleTimer class with asyncio-based per-session idle timers 60
path provides contains
telegram/session_manager.py Session metadata with idle_timeout field, PID tracking idle_timeout
path provides contains
telegram/claude_subprocess.py PID property for external access def pid
from to via pattern
telegram/idle_timer.py asyncio.create_task Background sleep task with cancellation asyncio.create_task.*_wait_for_timeout
from to via pattern
telegram/session_manager.py metadata.json idle_timeout stored in session metadata idle_timeout
Create the idle timer module and extend session metadata for lifecycle management.

Purpose: Foundation components needed before wiring suspend/resume into the bot. The idle timer provides per-session timeout detection, and metadata extensions store timeout configuration and subprocess PIDs. Output: New idle_timer.py module, updated session_manager.py and claude_subprocess.py

<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/03-lifecycle-management/03-CONTEXT.md @.planning/phases/03-lifecycle-management/03-RESEARCH.md @telegram/idle_timer.py (will be created) @telegram/session_manager.py @telegram/claude_subprocess.py Task 1: Create SessionIdleTimer module telegram/idle_timer.py Create `telegram/idle_timer.py` with a `SessionIdleTimer` class that manages per-session idle timeouts using asyncio.

Class design:

  • __init__(self, session_name: str, timeout_seconds: int, on_timeout: Callable[[str], Awaitable[None]]) -- stores config, initializes _timer_task to None, _last_activity to now (UTC)
  • reset(self) -- updates _last_activity to now, cancels existing _timer_task if running, creates new asyncio.create_task(_wait_for_timeout())
  • async _wait_for_timeout(self) -- awaits asyncio.sleep(self.timeout_seconds), then calls await self.on_timeout(self.session_name). Catches asyncio.CancelledError silently (timer was reset).
  • cancel(self) -- cancels _timer_task if running (used on shutdown/archive)
  • @property seconds_since_activity -- returns float seconds since _last_activity
  • @property last_activity -- returns the datetime of last activity (for /sessions display)

Use datetime.now(timezone.utc) for timestamps. Import typing for Callable, Optional, Awaitable.

Add module docstring explaining this is the idle timeout manager for session lifecycle. Log timer start/cancel/fire events at DEBUG level, timeout firing at INFO level. python3 -c "from idle_timer import SessionIdleTimer; print('import OK')" run from telegram/ directory succeeds. SessionIdleTimer class exists with reset(), cancel(), _wait_for_timeout(), seconds_since_activity, and last_activity. Imports cleanly.

Task 2: Extend session metadata and subprocess PID tracking telegram/session_manager.py, telegram/claude_subprocess.py **session_manager.py changes:**
  1. In create_session(), add "idle_timeout": 600 (10 minutes default) to the initial metadata dict (alongside existing fields like name, created, last_active, persona, pid, status).

  2. Add a helper method get_session_timeout(self, name: str) -> int that reads metadata and returns metadata.get('idle_timeout', 600). This provides a clean interface for the bot to query timeout values.

  3. No changes to list_sessions() -- it already returns full metadata which will now include idle_timeout.

claude_subprocess.py changes:

  1. Add a @property pid(self) -> Optional[int] that returns self._process.pid if self._process and self._process.returncode is None else None. This lets the bot store the PID in session metadata for orphan cleanup on restart.

  2. In start(), after successful subprocess spawn, store the PID in a self._pid attribute as well (for access even after process terminates, useful for logging). Keep the property returning live PID only.

These are minimal, targeted changes. Do NOT refactor existing code. Do NOT change the terminate() method or any existing logic. python3 -c "from session_manager import SessionManager; sm = SessionManager(); print('SM OK')" and python3 -c "from claude_subprocess import ClaudeSubprocess; print('CS OK')" both succeed from telegram/ directory. Session metadata includes idle_timeout (default 600s). SessionManager has get_session_timeout() method. ClaudeSubprocess has pid property returning live process PID.

- `cd ~/homelab/telegram && python3 -c "from idle_timer import SessionIdleTimer; from session_manager import SessionManager; from claude_subprocess import ClaudeSubprocess; print('All imports OK')"` - SessionIdleTimer has reset(), cancel(), seconds_since_activity, last_activity - SessionManager.get_session_timeout() returns int - ClaudeSubprocess.pid returns Optional[int]

<success_criteria>

  • idle_timer.py exists with SessionIdleTimer class implementing asyncio-based per-session idle timeout
  • session_manager.py creates sessions with idle_timeout=600 in metadata and has get_session_timeout() helper
  • claude_subprocess.py exposes pid property for PID tracking
  • All three modules import without errors </success_criteria>
After completion, create `.planning/phases/03-lifecycle-management/03-01-SUMMARY.md`