telegram-bot-mcp/docs/nexus-mcp-bridge-addendum.md
Mikkel Georgsen 494bb510d3 feat: add ingest API + health endpoint, fix bot-to-bot logging
Telegram bots can't see messages from other bots in groups. Added:
- POST /api/ingest - local services log messages into bridge DB
- GET /api/health - status check endpoint
- Fixed post_init not running (manual init lifecycle)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-30 08:39:44 +00:00

6.2 KiB

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:

# 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:

@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:

{
  "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

CHAT_SESSIONS = {
    "homelab": {
        "chat_id": "<telegram-group-id-homelab>",
        "workdir": "/home/mikkel/homelab",
        "claude_md": "/home/mikkel/homelab/.claude/CLAUDE.md",
        "session_file": "/home/mikkel/homelab/.claude-session",
    },
    "felt": {
        "chat_id": "<telegram-group-id-felt>",
        "workdir": "/home/mikkel/felt",
        "claude_md": "/home/mikkel/felt/.claude/CLAUDE.md",
        "session_file": "/home/mikkel/felt/.claude-session",
    },
    "sentry": {
        "chat_id": "<telegram-group-id-sentry>",
        "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:

# 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

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)

## 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: <task>. 
    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: <summary>]"
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: <first line of message>]"