# MCP Bridge Spec — Addendum: Session Tracking & Per-Chat Context Add these sections to the existing nexus-mcp-bridge-spec.md --- ## 11. Session Tracking Claude Code sessions are ephemeral. If a session dies and restarts, context is lost. The bridge must track session state to detect this. ### Agent-side: session file On startup, each Claude Code session writes its session ID to a known file in its working directory: ```python # Agent writes on boot (in CLAUDE.md startup hook or .bashrc) import uuid, json from datetime import datetime session = { "id": str(uuid.uuid4()), "started_at": datetime.utcnow().isoformat(), "chat_id": TELEGRAM_CHAT_ID, # Which chat this session serves "project": "homelab", # or "felt", "sentry", etc. } with open(os.path.expanduser("~/.claude-session"), "w") as f: json.dump(session, f) ``` ### Bridge-side: session awareness The bridge reads the session file on every poll. If the session ID changes between polls, it means the agent restarted: ```python @dataclass class SessionState: session_id: str started_at: datetime chat_id: str project: str last_seen_id: str | None = None # Previous session for comparison def check_session(self) -> tuple[SessionState, bool]: """Returns (current_session, has_restarted).""" with open(os.path.expanduser("~/.claude-session")) as f: data = json.load(f) current = SessionState(**data) restarted = (self.last_seen_id is not None and self.last_seen_id != current.session_id) self.last_seen_id = current.session_id return current, restarted ``` ### Restart notification When a restart is detected, pull_updates includes a warning: ```json { "updates": [ { "message_id": null, "content": "⚠️ Agent session restarted. Previous context lost. Session: abc-123 → def-456", "created_at": "2026-03-30T08:15:00Z", "type": "session_restart" } ] } ``` ### Persistent task state The agent persists its current task state to disk so it can resume after a restart instead of starting blind: ``` ~/.claude-session-state.json { "session_id": "abc-123", "current_task": "Fix nexus.mg DNS record", "current_task_id": "msg-456", "last_completed_step": 2, "pending_messages": ["msg-789"], "updated_at": "2026-03-30T01:15:00Z" } ``` On restart, the agent reads this file, announces what it was doing, and asks the owner whether to resume or start fresh. --- ## 12. Per-Chat Context Isolation Each Telegram group chat maps to a separate Claude Code session with its own context window, working directory, skills, and knowledge files. ### Chat → Session mapping ```python CHAT_SESSIONS = { "homelab": { "chat_id": "", "workdir": "/home/mikkel/homelab", "claude_md": "/home/mikkel/homelab/.claude/CLAUDE.md", "session_file": "/home/mikkel/homelab/.claude-session", }, "felt": { "chat_id": "", "workdir": "/home/mikkel/felt", "claude_md": "/home/mikkel/felt/.claude/CLAUDE.md", "session_file": "/home/mikkel/felt/.claude-session", }, "sentry": { "chat_id": "", "workdir": "/home/mikkel/sentry", "claude_md": "/home/mikkel/sentry/.claude/CLAUDE.md", "session_file": "/home/mikkel/sentry/.claude-session", }, } ``` ### Queue is per-chat Each chat has its own queue. Messages dispatched to "homelab" don't appear in the "felt" queue. The MCP tools gain a `target` parameter: ``` send_message(target="homelab", message="Fix the DNS record") send_message(target="felt", message="Update the Go backend") pull_updates(target="homelab") pull_updates(target="felt") queue_status() # Returns status for ALL targets ``` ### Session lifecycle per chat Each Claude Code session is started with its project-specific context: ```bash # Homelab session cd ~/homelab && claude --channels plugin:telegram@claude-plugins-official # Felt session cd ~/felt && claude --channels plugin:telegram@claude-plugins-official # Sentry session cd ~/sentry && claude --channels plugin:telegram@claude-plugins-official ``` Each session reads its own CLAUDE.md, has its own skills directory, and only sees messages from its own Telegram group. Context stays clean and focused. ### The bridge routes by target ```python async def send_message(target: str, message: str) -> str: session_config = CHAT_SESSIONS.get(target) if not session_config: return json.dumps({"queued": False, "reason": f"unknown target: {target}"}) msg = queues[target].enqueue(message) if msg is None: return json.dumps({"queued": False, "reason": "duplicate"}) await transport.send( chat_id=session_config["chat_id"], message=message, ) return json.dumps({"queued": True, "id": msg.id, "target": target}) ``` --- ## 13. Updated MCP Tools Summary | Tool | Parameters | Returns | |------|-----------|---------| | `send_message` | `target`, `message` | `{queued, id, target}` or `{reason}` | | `pull_updates` | `target`, `since?` | `{updates[], session_restart?}` | | `queue_status` | — | Per-target: `{pending, in_progress, current_task, session_id}` | --- ## 14. Agent System Prompt Addition (for CLAUDE.md) ```markdown ## Session Tracking On startup: 1. Generate a session UUID 2. Write it to ~/.claude-session with timestamp and project name 3. Read ~/.claude-session-state.json if it exists 4. If resuming from a crash, announce: "[Session restarted. Previous session was working on: . Resume or start fresh?]" 5. Persist task state to ~/.claude-session-state.json after every step ## Message Queue Awareness You receive messages from multiple sources: - Direct Telegram messages from Mikkel - Queued tasks from claude.ai via the MCP bridge When you receive a new message while working on a task: 1. Announce: "[New message received: ]" 2. Assess urgency — URGENT or STOP = halt immediately 3. If non-urgent, acknowledge and continue current task 4. Address queued messages in order after current task completes Always announce which message you're responding to: "[Responding to: ]" ```