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