---
phase: 01-session-process-foundation
plan: 01
type: execute
wave: 1
depends_on: []
files_modified:
- telegram/session_manager.py
- telegram/personas/default.json
- telegram/personas/brainstorm.json
- telegram/personas/planner.json
- telegram/personas/research.json
autonomous: true
must_haves:
truths:
- "SessionManager.create_session('test') creates directory at ~/telegram/sessions/test/ with metadata.json"
- "SessionManager.create_session('test', persona='brainstorm') copies brainstorm persona into session directory"
- "SessionManager.switch_session('test') updates active session and returns previous session name"
- "SessionManager.get_session('test') returns session metadata including name, status, timestamps"
- "Session names are validated: alphanumeric, hyphens, underscores only"
- "Persona library templates exist at ~/telegram/personas/ with at least default, brainstorm, planner, research"
artifacts:
- path: "telegram/session_manager.py"
provides: "Session lifecycle management"
min_lines: 80
contains: "class SessionManager"
- path: "telegram/personas/default.json"
provides: "Default persona template"
contains: "system_prompt"
- path: "telegram/personas/brainstorm.json"
provides: "Brainstorming persona template"
contains: "system_prompt"
key_links:
- from: "telegram/session_manager.py"
to: "telegram/personas/"
via: "persona library lookup on create_session"
pattern: "personas.*json"
- from: "telegram/session_manager.py"
to: "telegram/sessions/"
via: "directory creation and metadata writes"
pattern: "sessions.*metadata\\.json"
---
Create the session management module and persona library that provides the filesystem foundation for multi-session Claude Code conversations.
Purpose: Sessions are the core abstraction — each session is an isolated directory where a Claude Code subprocess will run (Plan 02) with its own conversation history, metadata, and persona configuration. This plan builds the session CRUD operations and persona template system.
Output: `telegram/session_manager.py` module and `telegram/personas/` directory with reusable persona templates.
@/home/mikkel/.claude/get-shit-done/workflows/execute-plan.md
@/home/mikkel/.claude/get-shit-done/templates/summary.md
@.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
@telegram/bot.py
Task 1: Create SessionManager module
telegram/session_manager.py
Create `telegram/session_manager.py` with a `SessionManager` class that manages session lifecycle.
**Session directory structure:**
```
~/telegram/sessions//
metadata.json # Session state
persona.json # Session persona (copied from library or custom)
.claude/ # Auto-created by Claude Code CLI
```
**SessionManager class design:**
```python
class SessionManager:
def __init__(self, base_dir: Path = None):
# base_dir defaults to ~/telegram/sessions/
# Also tracks personas_dir at ~/telegram/personas/
# Tracks active_session (str or None)
# Tracks all sessions dict[str, SessionMetadata]
```
**Required methods:**
1. `create_session(name: str, persona: str = None) -> Path`
- Validate name: regex `^[a-zA-Z0-9_-]+$`, max 50 chars
- If session already exists: raise ValueError with clear message (least-surprising: don't silently overwrite)
- Create directory at `sessions//`
- If persona specified: look up `personas/.json`, copy to `sessions//persona.json`
- If persona not specified: copy `personas/default.json` to session
- Write `metadata.json` with fields:
```json
{
"name": "session-name",
"created": "ISO-8601 timestamp",
"last_active": "ISO-8601 timestamp",
"persona": "persona-name-or-null",
"pid": null,
"status": "idle"
}
```
- Return session directory Path
- Status values: "idle" (no process), "active" (has running process, is current), "suspended" (has running process, not current)
2. `switch_session(name: str) -> str | None`
- If session doesn't exist: raise ValueError
- If already active: return None (no-op)
- Mark current active session as "suspended" in metadata (process stays alive per CONTEXT.md decision)
- Mark new session as "active" in metadata
- Update `last_active` timestamp on new session
- Update `self.active_session`
- Return previous session name (or None if no previous)
3. `get_session(name: str) -> dict`
- Read and return metadata.json contents for named session
- Raise ValueError if session doesn't exist
4. `list_sessions() -> list[dict]`
- Return list of all session metadata, sorted by last_active (most recent first)
5. `get_active_session() -> str | None`
- Return name of active session or None
6. `update_session(name: str, **kwargs) -> None`
- Update specific fields in session metadata (used by subprocess module to set PID, status)
7. `session_exists(name: str) -> bool`
- Check if session directory exists
8. `get_session_dir(name: str) -> Path`
- Return Path to session directory
9. `load_persona(name: str) -> dict`
- Load persona JSON from library (~/telegram/personas/.json)
- Return persona dict or raise FileNotFoundError
**Implementation notes:**
- Use `pathlib.Path` throughout
- Use `json` stdlib for metadata reads/writes
- Make metadata reads lazy (read from disk each time to avoid stale state)
- Add `logging` using `logging.getLogger(__name__)`
- Include type hints for all methods
- Add module docstring explaining the session model
**DO NOT:**
- Import or depend on claude_subprocess.py (that's Plan 02)
- Add Telegram-specific code (that's Plan 03)
- Implement idle timeout (that's Phase 3)
```bash
cd ~/homelab
source ~/venv/bin/activate
python3 -c "
from telegram.session_manager import SessionManager
sm = SessionManager()
# Create session
path = sm.create_session('test-session')
assert path.exists()
assert (path / 'metadata.json').exists()
assert (path / 'persona.json').exists()
meta = sm.get_session('test-session')
assert meta['name'] == 'test-session'
assert meta['status'] == 'idle'
# Switch session
sm.create_session('second-session')
prev = sm.switch_session('second-session')
assert prev is None # No previous active
assert sm.get_active_session() == 'second-session'
prev = sm.switch_session('test-session')
assert prev == 'second-session'
# List sessions
sessions = sm.list_sessions()
assert len(sessions) >= 2
# Validation
try:
sm.create_session('bad name!')
assert False, 'Should have raised ValueError'
except ValueError:
pass
# Cleanup
import shutil
shutil.rmtree(path.parent / 'test-session')
shutil.rmtree(path.parent / 'second-session')
print('All session manager tests passed!')
"
```
SessionManager creates isolated session directories with metadata, handles persona inheritance from library, validates names, switches active session correctly, and lists all sessions sorted by activity.
Task 2: Create persona library with default templates
telegram/personas/default.json
telegram/personas/brainstorm.json
telegram/personas/planner.json
telegram/personas/research.json
Create the persona library directory and four starter personas at `~/homelab/telegram/personas/`.
**Persona JSON schema:**
```json
{
"name": "persona-display-name",
"description": "One-line description of this persona's purpose",
"system_prompt": "The system prompt that shapes Claude's behavior in this session",
"settings": {
"model": "claude-sonnet-4-20250514",
"max_turns": 25
}
}
```
**Personas to create:**
1. **default.json** - General-purpose assistant
- system_prompt: "You are Claude, an AI assistant helping Mikkel manage his homelab infrastructure. You have full access to the management container's tools and can SSH to other containers. Be helpful, thorough, and proactive about suggesting improvements. When making changes, explain what you're doing and why."
- settings.model: "claude-sonnet-4-20250514" (cost-effective default)
- settings.max_turns: 25
2. **brainstorm.json** - Creative ideation mode
- system_prompt: "You are in brainstorming mode. Generate ideas freely without filtering. Build on previous ideas. Explore unconventional approaches. Ask probing questions to understand the problem space better. Don't worry about feasibility yet - that comes later. Output ideas as bullet lists for easy scanning."
- settings.model: "claude-sonnet-4-20250514"
- settings.max_turns: 50 (longer conversations for ideation)
3. **planner.json** - Structured planning mode
- system_prompt: "You are in planning mode. Break down complex tasks into clear, actionable steps. Identify dependencies and ordering. Estimate effort and flag risks. Use structured formats (numbered lists, tables) for clarity. Ask clarifying questions about requirements before diving into solutions."
- settings.model: "claude-sonnet-4-20250514"
- settings.max_turns: 30
4. **research.json** - Deep investigation mode
- system_prompt: "You are in research mode. Investigate topics thoroughly. Check documentation, source code, and configuration files. Cross-reference information. Cite your sources (file paths, URLs). Distinguish between facts and inferences. Summarize findings clearly with actionable recommendations."
- settings.model: "claude-sonnet-4-20250514"
- settings.max_turns: 30
**Notes:**
- The `settings` block will be consumed by the subprocess module (Plan 02) to configure Claude Code CLI flags
- Keep system_prompts concise but distinctive — each persona should feel like a different "mode"
- Use claude-sonnet-4-20250514 as default model (good balance of capability and cost for Telegram-driven sessions)
- The schema is intentionally simple for Phase 1; can be extended in future phases
```bash
cd ~/homelab
python3 -c "
import json
from pathlib import Path
personas_dir = Path('telegram/personas')
assert personas_dir.exists(), 'personas directory missing'
required = ['default.json', 'brainstorm.json', 'planner.json', 'research.json']
for name in required:
path = personas_dir / name
assert path.exists(), f'{name} missing'
data = json.loads(path.read_text())
assert 'name' in data, f'{name} missing name field'
assert 'description' in data, f'{name} missing description field'
assert 'system_prompt' in data, f'{name} missing system_prompt field'
assert 'settings' in data, f'{name} missing settings field'
assert 'model' in data['settings'], f'{name} missing settings.model'
assert 'max_turns' in data['settings'], f'{name} missing settings.max_turns'
print(f' {name}: OK ({data[\"name\"]})')
print('All persona templates valid!')
"
```
Four persona templates exist in ~/homelab/telegram/personas/ with valid JSON schema (name, description, system_prompt, settings.model, settings.max_turns). Each persona has a distinct system_prompt that shapes Claude's behavior differently.
1. `telegram/session_manager.py` exists with `SessionManager` class
2. `telegram/personas/` directory contains 4 valid persona JSON files
3. Creating a session writes metadata.json and copies persona to session directory
4. Session switching updates active session and marks previous as suspended
5. Session name validation rejects invalid characters
6. No imports from claude_subprocess or telegram bot modules
- SessionManager can create, list, switch, and query sessions purely via filesystem operations
- Persona library provides 4 distinct templates with consistent schema
- Session directories are fully isolated (each has own metadata.json and persona.json)
- All verification scripts pass without errors