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>
133 lines
6.2 KiB
Markdown
133 lines
6.2 KiB
Markdown
---
|
|
phase: 03-lifecycle-management
|
|
plan: 01
|
|
type: execute
|
|
wave: 1
|
|
depends_on: []
|
|
files_modified:
|
|
- telegram/idle_timer.py
|
|
- telegram/session_manager.py
|
|
- telegram/claude_subprocess.py
|
|
autonomous: true
|
|
|
|
must_haves:
|
|
truths:
|
|
- "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"
|
|
artifacts:
|
|
- path: "telegram/idle_timer.py"
|
|
provides: "SessionIdleTimer class with asyncio-based per-session idle timers"
|
|
min_lines: 60
|
|
- path: "telegram/session_manager.py"
|
|
provides: "Session metadata with idle_timeout field, PID tracking"
|
|
contains: "idle_timeout"
|
|
- path: "telegram/claude_subprocess.py"
|
|
provides: "PID property for external access"
|
|
contains: "def pid"
|
|
key_links:
|
|
- from: "telegram/idle_timer.py"
|
|
to: "asyncio.create_task"
|
|
via: "Background sleep task with cancellation"
|
|
pattern: "asyncio\\.create_task.*_wait_for_timeout"
|
|
- from: "telegram/session_manager.py"
|
|
to: "metadata.json"
|
|
via: "idle_timeout stored in session metadata"
|
|
pattern: "idle_timeout"
|
|
---
|
|
|
|
<objective>
|
|
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`
|
|
</objective>
|
|
|
|
<execution_context>
|
|
@/home/mikkel/.claude/get-shit-done/workflows/execute-plan.md
|
|
@/home/mikkel/.claude/get-shit-done/templates/summary.md
|
|
</execution_context>
|
|
|
|
<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
|
|
</context>
|
|
|
|
<tasks>
|
|
|
|
<task type="auto">
|
|
<name>Task 1: Create SessionIdleTimer module</name>
|
|
<files>telegram/idle_timer.py</files>
|
|
<action>
|
|
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.
|
|
</action>
|
|
<verify>
|
|
`python3 -c "from idle_timer import SessionIdleTimer; print('import OK')"` run from telegram/ directory succeeds.
|
|
</verify>
|
|
<done>SessionIdleTimer class exists with reset(), cancel(), _wait_for_timeout(), seconds_since_activity, and last_activity. Imports cleanly.</done>
|
|
</task>
|
|
|
|
<task type="auto">
|
|
<name>Task 2: Extend session metadata and subprocess PID tracking</name>
|
|
<files>telegram/session_manager.py, telegram/claude_subprocess.py</files>
|
|
<action>
|
|
**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.
|
|
</action>
|
|
<verify>
|
|
`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.
|
|
</verify>
|
|
<done>Session metadata includes idle_timeout (default 600s). SessionManager has get_session_timeout() method. ClaudeSubprocess has pid property returning live process PID.</done>
|
|
</task>
|
|
|
|
</tasks>
|
|
|
|
<verification>
|
|
- `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]
|
|
</verification>
|
|
|
|
<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>
|
|
|
|
<output>
|
|
After completion, create `.planning/phases/03-lifecycle-management/03-01-SUMMARY.md`
|
|
</output>
|