From 0bc43c470ed67746602ce32e2e1f7f616c477173 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 27 Jan 2026 21:03:11 +0000 Subject: [PATCH 1/3] Initial plan From 3ee571749bb3539b9ed30adf1edacb846aa63bb7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 27 Jan 2026 21:08:16 +0000 Subject: [PATCH 2/3] Implement session collaboration and memory system Co-authored-by: blackboxprogramming <118287761+blackboxprogramming@users.noreply.github.com> --- prototypes/sessions/README.md | 458 +++++++++++++++++ prototypes/sessions/sessions/__init__.py | 22 + prototypes/sessions/sessions/__main__.py | 7 + prototypes/sessions/sessions/cli.py | 361 ++++++++++++++ prototypes/sessions/sessions/collaboration.py | 431 ++++++++++++++++ prototypes/sessions/sessions/memory.py | 469 ++++++++++++++++++ prototypes/sessions/sessions/registry.py | 368 ++++++++++++++ prototypes/sessions/setup.py | 26 + prototypes/sessions/test_sessions.py | 307 ++++++++++++ 9 files changed, 2449 insertions(+) create mode 100644 prototypes/sessions/README.md create mode 100644 prototypes/sessions/sessions/__init__.py create mode 100644 prototypes/sessions/sessions/__main__.py create mode 100644 prototypes/sessions/sessions/cli.py create mode 100644 prototypes/sessions/sessions/collaboration.py create mode 100644 prototypes/sessions/sessions/memory.py create mode 100644 prototypes/sessions/sessions/registry.py create mode 100644 prototypes/sessions/setup.py create mode 100644 prototypes/sessions/test_sessions.py diff --git a/prototypes/sessions/README.md b/prototypes/sessions/README.md new file mode 100644 index 0000000..4f43ad4 --- /dev/null +++ b/prototypes/sessions/README.md @@ -0,0 +1,458 @@ +# BlackRoad Session Management + +**[COLLABORATION] + [MEMORY] for the Mesh** + +Enables multiple AI/agent sessions to discover each other, communicate, and share state across the BlackRoad ecosystem. + +## Overview + +The Session Management system provides three core capabilities: + +1. **Session Registry** - Track active sessions +2. **Collaboration Hub** - Inter-session communication +3. **Shared Memory** - Cross-session state storage + +## Quick Start + +### Install + +```bash +cd prototypes/sessions +pip install -e . +``` + +### Register a Session + +```bash +python -m sessions register \ + "cece-001" \ + "Cece" \ + "Claude" \ + --user "Alexa" \ + --capabilities "python,review,planning" +``` + +### List Active Sessions + +```bash +python -m sessions list +``` + +Output: +``` +SESSION ID AGENT TYPE STATUS USER +================================================================================== +cece-001 Cece Claude active Alexa +agent-002 Agent-2 GPT-4 working Alexa + +πŸ“Š Stats: + Total: 2 + Active: 2 + By status: {'active': 1, 'working': 1} +``` + +### Ping Another Session + +```bash +# From Python +from sessions import CollaborationHub + +hub = CollaborationHub() +msg = hub.ping_session("cece-001", "agent-002") +print(msg.format_signal()) +# Output: πŸ”” cece-001 β†’ agent-002 : [COLLABORATION] Ping +``` + +### Send a Message + +```bash +python -m sessions send \ + "cece-001" \ + "agent-002" \ + "Need code review" \ + "Can you review my Python changes?" \ + --type request +``` + +### Broadcast to All Sessions + +```bash +python -m sessions broadcast \ + "cece-001" \ + "Deployment starting" \ + "Starting production deployment in 5 minutes" +``` + +### Store in Shared Memory + +```bash +python -m sessions memory-set \ + "cece-001" \ + "current_task" \ + "Building collaboration system" \ + --type state \ + --tags "task,active" +``` + +### Read from Shared Memory + +```bash +python -m sessions memory-get "current_task" +# Output: βœ… Value: Building collaboration system +``` + +## Python API + +### Session Registry + +```python +from sessions import SessionRegistry, SessionStatus + +registry = SessionRegistry() + +# Register a new session +session = registry.register( + session_id="cece-001", + agent_name="Cece", + agent_type="Claude", + human_user="Alexa", + capabilities=["python", "review", "planning"] +) + +# List active sessions +sessions = registry.list_sessions() + +# Ping to keep alive +registry.ping("cece-001") + +# Update status +registry.update_status( + "cece-001", + SessionStatus.WORKING, + current_task="Code review" +) + +# Find sessions +active = registry.find_sessions(status=SessionStatus.ACTIVE) +python_experts = registry.find_sessions(capability="python") + +# Get stats +stats = registry.get_stats() +``` + +### Collaboration Hub + +```python +from sessions import CollaborationHub, MessageType + +hub = CollaborationHub() + +# Send a direct message +msg = hub.send( + from_session="cece-001", + to_session="agent-002", + type=MessageType.REQUEST, + subject="Need help", + body="Can you assist with this task?", + data={"task_id": "123", "priority": "high"} +) + +# Broadcast to all +hub.broadcast( + from_session="cece-001", + subject="System update", + body="Deploying new version" +) + +# Reply to a message +hub.reply( + from_session="agent-002", + to_message=msg, + body="Sure, I can help!" +) + +# Get messages for a session +messages = hub.get_messages("agent-002") + +# Ping another session +hub.ping_session("cece-001", "agent-002") + +# Get full conversation +thread = hub.get_conversation(msg.message_id) +``` + +### Shared Memory + +```python +from sessions import SharedMemory, MemoryType + +memory = SharedMemory() + +# Store a value +memory.set( + session_id="cece-001", + key="current_task", + value={"name": "Build collaboration", "status": "in_progress"}, + type=MemoryType.STATE, + tags=["task", "active"] +) + +# Get most recent value +task = memory.get("current_task") + +# Get all values for a key +all_tasks = memory.get_all("current_task") + +# Search by pattern +tasks = memory.search("task_*") + +# Get by tags +active_items = memory.get_by_tags(["active", "task"]) + +# Get by session +my_entries = memory.get_by_session("cece-001") + +# Delete +memory.delete("old_key") + +# Get stats +stats = memory.get_stats() +``` + +## Message Types + +| Type | Use Case | +|------|----------| +| `PING` | Simple ping/pong to check if session is responsive | +| `REQUEST` | Request help or action from another session | +| `RESPONSE` | Respond to a request | +| `BROADCAST` | Send to all sessions | +| `NOTIFICATION` | Alert about an event | +| `TASK_OFFER` | Offer to take on a task | +| `TASK_ACCEPT` | Accept a task offer | +| `SYNC` | Request synchronization | +| `HANDOFF` | Hand off a task to another session | + +## Memory Types + +| Type | Use Case | +|------|----------| +| `STATE` | Current session state | +| `FACT` | Learned fact or knowledge | +| `DECISION` | Decision that was made | +| `TASK` | Task information | +| `CONTEXT` | Background context | +| `NOTE` | General note | +| `CONFIG` | Configuration setting | + +## Architecture + +``` +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ SESSION MANAGEMENT β”‚ +β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ +β”‚ β”‚ +β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ +β”‚ β”‚ Registry β”‚ β”‚ Collaborationβ”‚ β”‚Shared Memory β”‚ β”‚ +β”‚ β”‚ β”‚ β”‚ Hub β”‚ β”‚ β”‚ β”‚ +β”‚ β”‚ β€’ Track β”‚ β”‚ β€’ Messages β”‚ β”‚ β€’ Key-Value β”‚ β”‚ +β”‚ β”‚ β€’ Discover β”‚ β”‚ β€’ Broadcast β”‚ β”‚ β€’ Search β”‚ β”‚ +β”‚ β”‚ β€’ Ping β”‚ β”‚ β€’ Threads β”‚ β”‚ β€’ TTL β”‚ β”‚ +β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ +β”‚ β”‚ β”‚ β”‚ β”‚ +β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ +β”‚ β”‚ β”‚ +β”‚ β–Ό β”‚ +β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ +β”‚ β”‚ .sessions/ β”‚ β”‚ +β”‚ β”‚ β”‚ β”‚ +β”‚ β”‚ β€’ Registry β”‚ β”‚ +β”‚ β”‚ β€’ Messages β”‚ β”‚ +β”‚ β”‚ β€’ Memory β”‚ β”‚ +β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ +β”‚ β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +## Data Storage + +All session data is stored in `.sessions/` directory: + +``` +.sessions/ +β”œβ”€β”€ active_sessions.json # Session registry +β”œβ”€β”€ messages/ +β”‚ └── recent_messages.json +└── shared_memory/ + └── memory.json +``` + +## Use Cases + +### 1. Session Discovery + +```python +# Find all active sessions +registry = SessionRegistry() +sessions = registry.list_sessions() + +for session in sessions: + print(f"{session.agent_name} ({session.agent_type}) - {session.status.value}") +``` + +### 2. Collaborative Code Review + +```python +hub = CollaborationHub() + +# Session 1: Request review +msg = hub.send( + from_session="cece-001", + to_session="reviewer-002", + type=MessageType.REQUEST, + subject="Code review needed", + body="Please review PR #123", + data={"pr": 123, "files": ["main.py", "test.py"]} +) + +# Session 2: Accept and respond +hub.reply( + from_session="reviewer-002", + to_message=msg, + body="Reviewed. LGTM with minor comments.", + data={"approved": True, "comments": 2} +) +``` + +### 3. Task Handoff + +```python +# Session 1: Can't complete task +hub.send( + from_session="session-1", + to_session="session-2", + type=MessageType.HANDOFF, + subject="Handing off deployment", + body="I need to disconnect. Can you take over?", + data={"task": "deploy-prod", "stage": "testing"} +) + +# Session 2: Picks up where session-1 left off +memory = SharedMemory() +deploy_state = memory.get("deploy_state") +# Continue deployment... +``` + +### 4. Shared Context + +```python +memory = SharedMemory() + +# Session 1: Store findings +memory.set( + session_id="session-1", + key="api_endpoints", + value=["GET /users", "POST /users", "DELETE /users/:id"], + type=MemoryType.FACT, + tags=["api", "documentation"] +) + +# Session 2: Access findings +endpoints = memory.get("api_endpoints") +# Use the discovered endpoints... +``` + +## Integration with Bridge + +The session system integrates with existing Bridge infrastructure: + +1. **Signals** - Messages generate signal events +2. **MCP Server** - Exposed via MCP tools +3. **Dispatcher** - Can route to sessions +4. **Status Beacon** - Shows active sessions + +## CLI Commands + +```bash +# Session management +python -m sessions register [--user USER] +python -m sessions list [--all] +python -m sessions ping +python -m sessions status [--task TASK] + +# Collaboration +python -m sessions send [--type TYPE] +python -m sessions broadcast +python -m sessions messages [--type TYPE] + +# Shared memory +python -m sessions memory-set [--type TYPE] [--tags TAGS] +python -m sessions memory-get [--all] +python -m sessions memory-search [--pattern PATTERN] [--tags TAGS] [--session SESSION] + +# Statistics +python -m sessions stats +``` + +## Example: Multi-Session Workflow + +```python +from sessions import SessionRegistry, CollaborationHub, SharedMemory, MessageType, MemoryType + +# Initialize +registry = SessionRegistry() +hub = CollaborationHub() +memory = SharedMemory() + +# Session 1: Planning agent +registry.register("planner-001", "Planner", "Claude", human_user="Alexa") +memory.set("planner-001", "project_plan", { + "phase": "design", + "tasks": ["architecture", "api-design", "database"] +}, type=MemoryType.STATE, tags=["project", "active"]) + +hub.broadcast("planner-001", "Project started", "Beginning design phase") + +# Session 2: Developer agent +registry.register("dev-001", "Developer", "GPT-4", human_user="Alexa") + +# Dev reads plan from memory +plan = memory.get("project_plan") + +# Dev requests clarification +hub.send("dev-001", "planner-001", MessageType.REQUEST, + "API design question", "Should we use REST or GraphQL?") + +# Session 3: Reviewer agent +registry.register("reviewer-001", "Reviewer", "Claude", human_user="Alexa") + +# Later: Dev hands off to reviewer +memory.set("dev-001", "api_code", "class API...", + type=MemoryType.STATE, tags=["code", "ready-for-review"]) + +hub.send("dev-001", "reviewer-001", MessageType.TASK_OFFER, + "Code review", "API implementation ready", + data={"files": ["api.py"], "tests": "passing"}) + +# Reviewer accepts +hub.send("reviewer-001", "dev-001", MessageType.TASK_ACCEPT, + "Starting review", "Will review and provide feedback") + +# Show stats +print(registry.get_stats()) +print(hub.get_stats()) +print(memory.get_stats()) +``` + +## Future Enhancements + +- WebSocket support for real-time updates +- Session groups/teams +- Priority queues for messages +- Memory replication across nodes +- Integration with RoadChain for audit trail +- Session metrics and analytics + +--- + +*Part of the BlackRoad Bridge - Where sessions collaborate.* diff --git a/prototypes/sessions/sessions/__init__.py b/prototypes/sessions/sessions/__init__.py new file mode 100644 index 0000000..d00de69 --- /dev/null +++ b/prototypes/sessions/sessions/__init__.py @@ -0,0 +1,22 @@ +""" +BlackRoad Session Management - Collaboration & Memory. + +Enables multiple AI/agent sessions to discover, communicate, and share state. +""" + +from .registry import SessionRegistry, Session, SessionStatus +from .collaboration import CollaborationHub, Message, MessageType +from .memory import SharedMemory, MemoryEntry + +__all__ = [ + "SessionRegistry", + "Session", + "SessionStatus", + "CollaborationHub", + "Message", + "MessageType", + "SharedMemory", + "MemoryEntry", +] + +__version__ = "0.1.0" diff --git a/prototypes/sessions/sessions/__main__.py b/prototypes/sessions/sessions/__main__.py new file mode 100644 index 0000000..797be18 --- /dev/null +++ b/prototypes/sessions/sessions/__main__.py @@ -0,0 +1,7 @@ +#!/usr/bin/env python3 +"""Main entry point for sessions CLI.""" + +from sessions.cli import main + +if __name__ == "__main__": + main() diff --git a/prototypes/sessions/sessions/cli.py b/prototypes/sessions/sessions/cli.py new file mode 100644 index 0000000..24b9697 --- /dev/null +++ b/prototypes/sessions/sessions/cli.py @@ -0,0 +1,361 @@ +""" +CLI for BlackRoad Session Management. + +Command-line interface for session discovery, collaboration, and memory. +""" + +import sys +import json +import asyncio +from pathlib import Path +from typing import Optional + +# Add parent to path for imports +sys.path.insert(0, str(Path(__file__).parent.parent)) + +from sessions.registry import SessionRegistry, SessionStatus +from sessions.collaboration import CollaborationHub, MessageType +from sessions.memory import SharedMemory, MemoryType + + +def cmd_register(args): + """Register a new session.""" + registry = SessionRegistry() + + session = registry.register( + session_id=args.session_id, + agent_name=args.agent_name, + agent_type=args.agent_type, + human_user=args.user, + capabilities=args.capabilities.split(',') if args.capabilities else [], + ) + + print(f"βœ… Registered session: {session.session_id}") + print(f" Agent: {session.agent_name} ({session.agent_type})") + print(f" User: {session.human_user}") + print(f" Started: {session.started_at}") + + +def cmd_list(args): + """List active sessions.""" + registry = SessionRegistry() + sessions = registry.list_sessions(include_offline=args.all) + + if not sessions: + print("No active sessions found.") + return + + print(f"\n{'SESSION ID':<20} {'AGENT':<15} {'TYPE':<10} {'STATUS':<10} {'USER':<15}") + print("=" * 90) + + for session in sessions: + print(f"{session.session_id:<20} {session.agent_name:<15} {session.agent_type:<10} " + f"{session.status.value:<10} {session.human_user or 'N/A':<15}") + + # Print stats + print(f"\nπŸ“Š Stats:") + stats = registry.get_stats() + print(f" Total: {stats['total_sessions']}") + print(f" Active: {stats['active_sessions']}") + print(f" By status: {stats['by_status']}") + + +def cmd_ping(args): + """Ping a session.""" + registry = SessionRegistry() + + if registry.ping(args.session_id): + print(f"βœ… Pinged session: {args.session_id}") + else: + print(f"❌ Session not found: {args.session_id}") + + +def cmd_status(args): + """Update session status.""" + registry = SessionRegistry() + + status = SessionStatus(args.status) + + if registry.update_status(args.session_id, status, args.task): + print(f"βœ… Updated session {args.session_id}") + print(f" Status: {status.value}") + if args.task: + print(f" Task: {args.task}") + else: + print(f"❌ Session not found: {args.session_id}") + + +def cmd_send(args): + """Send a message to another session.""" + hub = CollaborationHub() + + message = hub.send( + from_session=args.from_session, + to_session=args.to_session, + type=MessageType(args.type), + subject=args.subject, + body=args.body, + data=json.loads(args.data) if args.data else {}, + ) + + print(f"βœ… Message sent: {message.message_id}") + print(f" {message.format_signal()}") + + +def cmd_broadcast(args): + """Broadcast a message to all sessions.""" + hub = CollaborationHub() + + message = hub.broadcast( + from_session=args.from_session, + subject=args.subject, + body=args.body, + data=json.loads(args.data) if args.data else {}, + ) + + print(f"βœ… Broadcast sent: {message.message_id}") + print(f" {message.format_signal()}") + + +def cmd_messages(args): + """Get messages for a session.""" + hub = CollaborationHub() + + messages = hub.get_messages( + session_id=args.session_id, + message_type=MessageType(args.type) if args.type else None, + ) + + if not messages: + print(f"No messages for session: {args.session_id}") + return + + print(f"\nπŸ“¬ Messages for {args.session_id}:") + print("=" * 90) + + for msg in messages: + target = msg.to_session or "ALL" + print(f"\n{msg.timestamp}") + print(f" From: {msg.from_session} β†’ {target}") + print(f" Type: {msg.type.value}") + print(f" Subject: {msg.subject}") + print(f" Body: {msg.body}") + if msg.data: + print(f" Data: {json.dumps(msg.data, indent=4)}") + + +def cmd_memory_set(args): + """Store a value in shared memory.""" + memory = SharedMemory() + + # Parse value as JSON if possible + try: + value = json.loads(args.value) + except: + value = args.value + + entry = memory.set( + session_id=args.session_id, + key=args.key, + value=value, + type=MemoryType(args.type), + tags=args.tags.split(',') if args.tags else [], + ) + + print(f"βœ… Stored in shared memory") + print(f" Key: {entry.key}") + print(f" Type: {entry.type.value}") + print(f" Value: {entry.value}") + print(f" Entry ID: {entry.entry_id}") + + +def cmd_memory_get(args): + """Get a value from shared memory.""" + memory = SharedMemory() + + if args.all: + entries = memory.get_all(args.key) + + if not entries: + print(f"No entries found for key: {args.key}") + return + + print(f"\nπŸ“ Entries for key '{args.key}':") + print("=" * 90) + + for entry in entries: + print(f"\n{entry.timestamp} (by {entry.session_id})") + print(f" Type: {entry.type.value}") + print(f" Value: {entry.value}") + if entry.tags: + print(f" Tags: {', '.join(entry.tags)}") + else: + value = memory.get(args.key) + + if value is None: + print(f"No value found for key: {args.key}") + else: + print(f"βœ… Value: {value}") + + +def cmd_memory_search(args): + """Search shared memory.""" + memory = SharedMemory() + + if args.pattern: + entries = memory.search(args.pattern) + elif args.tags: + tags = args.tags.split(',') + entries = memory.get_by_tags(tags) + elif args.session: + entries = memory.get_by_session(args.session) + else: + print("Error: Specify --pattern, --tags, or --session") + return + + if not entries: + print("No entries found.") + return + + print(f"\nπŸ“ Found {len(entries)} entries:") + print("=" * 90) + + for entry in entries[:20]: # Limit to 20 + print(f"\n{entry.timestamp}") + print(f" Key: {entry.key}") + print(f" Type: {entry.type.value}") + print(f" Value: {entry.value}") + print(f" Session: {entry.session_id}") + if entry.tags: + print(f" Tags: {', '.join(entry.tags)}") + + +def cmd_stats(args): + """Show statistics.""" + registry = SessionRegistry() + hub = CollaborationHub() + memory = SharedMemory() + + print("\nπŸ“Š BlackRoad Session Statistics") + print("=" * 60) + + print("\nπŸ”— Sessions:") + session_stats = registry.get_stats() + print(f" Total: {session_stats['total_sessions']}") + print(f" Active: {session_stats['active_sessions']}") + print(f" By status: {json.dumps(session_stats['by_status'], indent=4)}") + + print("\nπŸ’¬ Collaboration:") + collab_stats = hub.get_stats() + print(f" Total messages: {collab_stats['total_messages']}") + print(f" By type: {json.dumps(collab_stats['by_type'], indent=4)}") + + print("\n🧠 Shared Memory:") + memory_stats = memory.get_stats() + print(f" Total entries: {memory_stats['total_entries']}") + print(f" Unique keys: {memory_stats['unique_keys']}") + print(f" By type: {json.dumps(memory_stats['by_type'], indent=4)}") + + +def main(): + """Main CLI entry point.""" + import argparse + + parser = argparse.ArgumentParser(description="BlackRoad Session Management") + subparsers = parser.add_subparsers(dest='command', help='Command to execute') + + # Register command + register_parser = subparsers.add_parser('register', help='Register a new session') + register_parser.add_argument('session_id', help='Session ID') + register_parser.add_argument('agent_name', help='Agent name') + register_parser.add_argument('agent_type', help='Agent type') + register_parser.add_argument('--user', help='Human user') + register_parser.add_argument('--capabilities', help='Comma-separated capabilities') + + # List command + list_parser = subparsers.add_parser('list', help='List active sessions') + list_parser.add_argument('--all', action='store_true', help='Include offline sessions') + + # Ping command + ping_parser = subparsers.add_parser('ping', help='Ping a session') + ping_parser.add_argument('session_id', help='Session to ping') + + # Status command + status_parser = subparsers.add_parser('status', help='Update session status') + status_parser.add_argument('session_id', help='Session ID') + status_parser.add_argument('status', choices=[s.value for s in SessionStatus]) + status_parser.add_argument('--task', help='Current task') + + # Send command + send_parser = subparsers.add_parser('send', help='Send a message') + send_parser.add_argument('from_session', help='Sender session ID') + send_parser.add_argument('to_session', help='Recipient session ID') + send_parser.add_argument('subject', help='Message subject') + send_parser.add_argument('body', help='Message body') + send_parser.add_argument('--type', default='request', choices=[t.value for t in MessageType]) + send_parser.add_argument('--data', help='JSON data') + + # Broadcast command + broadcast_parser = subparsers.add_parser('broadcast', help='Broadcast a message') + broadcast_parser.add_argument('from_session', help='Sender session ID') + broadcast_parser.add_argument('subject', help='Message subject') + broadcast_parser.add_argument('body', help='Message body') + broadcast_parser.add_argument('--data', help='JSON data') + + # Messages command + messages_parser = subparsers.add_parser('messages', help='Get messages') + messages_parser.add_argument('session_id', help='Session ID') + messages_parser.add_argument('--type', choices=[t.value for t in MessageType]) + + # Memory set command + memory_set_parser = subparsers.add_parser('memory-set', help='Store in shared memory') + memory_set_parser.add_argument('session_id', help='Session ID') + memory_set_parser.add_argument('key', help='Memory key') + memory_set_parser.add_argument('value', help='Value to store') + memory_set_parser.add_argument('--type', default='state', choices=[t.value for t in MemoryType]) + memory_set_parser.add_argument('--tags', help='Comma-separated tags') + + # Memory get command + memory_get_parser = subparsers.add_parser('memory-get', help='Get from shared memory') + memory_get_parser.add_argument('key', help='Memory key') + memory_get_parser.add_argument('--all', action='store_true', help='Get all entries') + + # Memory search command + memory_search_parser = subparsers.add_parser('memory-search', help='Search shared memory') + memory_search_parser.add_argument('--pattern', help='Key pattern') + memory_search_parser.add_argument('--tags', help='Comma-separated tags') + memory_search_parser.add_argument('--session', help='Session ID') + + # Stats command + stats_parser = subparsers.add_parser('stats', help='Show statistics') + + args = parser.parse_args() + + if not args.command: + parser.print_help() + return + + # Execute command + command_map = { + 'register': cmd_register, + 'list': cmd_list, + 'ping': cmd_ping, + 'status': cmd_status, + 'send': cmd_send, + 'broadcast': cmd_broadcast, + 'messages': cmd_messages, + 'memory-set': cmd_memory_set, + 'memory-get': cmd_memory_get, + 'memory-search': cmd_memory_search, + 'stats': cmd_stats, + } + + handler = command_map.get(args.command) + if handler: + handler(args) + else: + print(f"Unknown command: {args.command}") + + +if __name__ == "__main__": + main() diff --git a/prototypes/sessions/sessions/collaboration.py b/prototypes/sessions/sessions/collaboration.py new file mode 100644 index 0000000..d41ab95 --- /dev/null +++ b/prototypes/sessions/sessions/collaboration.py @@ -0,0 +1,431 @@ +""" +Collaboration Hub - Enable inter-session communication. + +Provides message passing, task coordination, and collaboration +capabilities between multiple active sessions. +""" + +import json +from pathlib import Path +from dataclasses import dataclass, asdict +from datetime import datetime +from typing import Dict, List, Optional, Any, Callable +from enum import Enum +import uuid + +from .registry import SessionRegistry, Session + + +class MessageType(Enum): + """Types of collaboration messages.""" + PING = "ping" # Simple ping/pong + REQUEST = "request" # Request for help/action + RESPONSE = "response" # Response to request + BROADCAST = "broadcast" # Broadcast to all sessions + NOTIFICATION = "notification" # Notification/alert + TASK_OFFER = "task_offer" # Offer to take on a task + TASK_ACCEPT = "task_accept" # Accept task offer + SYNC = "sync" # Sync request + HANDOFF = "handoff" # Hand off task to another session + + +@dataclass +class Message: + """A collaboration message between sessions.""" + + message_id: str + type: MessageType + from_session: str + to_session: Optional[str] # None for broadcast + subject: str + body: str + data: Dict[str, Any] + timestamp: str + in_reply_to: Optional[str] = None + + def __post_init__(self): + """Initialize timestamp if not provided.""" + if not self.timestamp: + self.timestamp = datetime.utcnow().isoformat() + + def to_dict(self) -> Dict[str, Any]: + """Convert to dictionary.""" + data = asdict(self) + data['type'] = self.type.value + return data + + @classmethod + def from_dict(cls, data: Dict[str, Any]) -> 'Message': + """Create from dictionary.""" + if 'type' in data and isinstance(data['type'], str): + data['type'] = MessageType(data['type']) + return cls(**data) + + def format_signal(self) -> str: + """Format as a signal string.""" + emoji_map = { + MessageType.PING: "πŸ””", + MessageType.REQUEST: "❓", + MessageType.RESPONSE: "βœ…", + MessageType.BROADCAST: "πŸ“‘", + MessageType.NOTIFICATION: "πŸ“’", + MessageType.TASK_OFFER: "🀝", + MessageType.TASK_ACCEPT: "πŸ‘", + MessageType.SYNC: "πŸ”„", + MessageType.HANDOFF: "🎯", + } + + emoji = emoji_map.get(self.type, "πŸ’¬") + target = self.to_session or "ALL" + + return f"{emoji} {self.from_session} β†’ {target} : [COLLABORATION] {self.subject}" + + +class CollaborationHub: + """ + Hub for session collaboration and communication. + + Enables sessions to: + - Send messages to each other + - Broadcast to all sessions + - Coordinate on tasks + - Share work and handoff tasks + + Usage: + hub = CollaborationHub() + + # Register sessions first + hub.registry.register("session-1", "Cece", "Claude", "Alexa") + hub.registry.register("session-2", "Agent-2", "GPT-4", "Alexa") + + # Send a message + msg = hub.send( + from_session="session-1", + to_session="session-2", + type=MessageType.REQUEST, + subject="Need help with Python", + body="Can you review this code?", + data={"code": "..."} + ) + + # Broadcast to all + hub.broadcast( + from_session="session-1", + subject="Deployment starting", + body="Starting deployment to production" + ) + + # Get messages for a session + messages = hub.get_messages("session-2") + + # Reply to a message + hub.reply( + from_session="session-2", + to_message=msg, + body="Sure, looks good!" + ) + """ + + def __init__( + self, + registry: Optional[SessionRegistry] = None, + messages_path: Optional[Path] = None + ): + """ + Initialize the collaboration hub. + + Args: + registry: Session registry (created if None) + messages_path: Path to store messages + """ + self.registry = registry or SessionRegistry() + + if messages_path: + self.messages_path = Path(messages_path) + else: + self.messages_path = self.registry.registry_path / "messages" + + self.messages_path.mkdir(exist_ok=True) + + self._message_handlers: Dict[MessageType, List[Callable]] = {} + self._messages: List[Message] = [] + + self._load_messages() + + def _load_messages(self): + """Load recent messages from disk.""" + messages_file = self.messages_path / "recent_messages.json" + + if not messages_file.exists(): + return + + try: + with open(messages_file, 'r') as f: + data = json.load(f) + + self._messages = [ + Message.from_dict(msg_data) + for msg_data in data.get('messages', []) + ] + + except Exception as e: + print(f"Warning: Could not load messages: {e}") + + def _save_messages(self): + """Save recent messages to disk.""" + messages_file = self.messages_path / "recent_messages.json" + + # Keep only recent messages (last 100) + recent = self._messages[-100:] + + try: + data = { + 'messages': [msg.to_dict() for msg in recent], + 'updated_at': datetime.utcnow().isoformat(), + } + + with open(messages_file, 'w') as f: + json.dump(data, f, indent=2) + + except Exception as e: + print(f"Warning: Could not save messages: {e}") + + def send( + self, + from_session: str, + to_session: str, + type: MessageType, + subject: str, + body: str, + data: Optional[Dict[str, Any]] = None, + in_reply_to: Optional[str] = None, + ) -> Message: + """ + Send a message to another session. + + Args: + from_session: Sender session ID + to_session: Recipient session ID + type: Message type + subject: Message subject + body: Message body + data: Additional data + in_reply_to: Message ID this is replying to + + Returns: + The sent Message object + """ + message = Message( + message_id=str(uuid.uuid4()), + type=type, + from_session=from_session, + to_session=to_session, + subject=subject, + body=body, + data=data or {}, + timestamp=datetime.utcnow().isoformat(), + in_reply_to=in_reply_to, + ) + + self._messages.append(message) + self._save_messages() + + # Print signal + print(f" {message.format_signal()}") + + # Trigger handlers + self._trigger_handlers(message) + + return message + + def broadcast( + self, + from_session: str, + subject: str, + body: str, + data: Optional[Dict[str, Any]] = None, + ) -> Message: + """ + Broadcast a message to all sessions. + + Args: + from_session: Sender session ID + subject: Message subject + body: Message body + data: Additional data + + Returns: + The broadcast Message object + """ + message = Message( + message_id=str(uuid.uuid4()), + type=MessageType.BROADCAST, + from_session=from_session, + to_session=None, + subject=subject, + body=body, + data=data or {}, + timestamp=datetime.utcnow().isoformat(), + ) + + self._messages.append(message) + self._save_messages() + + # Print signal + print(f" {message.format_signal()}") + + # Trigger handlers + self._trigger_handlers(message) + + return message + + def reply( + self, + from_session: str, + to_message: Message, + body: str, + data: Optional[Dict[str, Any]] = None, + ) -> Message: + """ + Reply to a message. + + Args: + from_session: Sender session ID + to_message: Message being replied to + body: Reply body + data: Additional data + + Returns: + The reply Message object + """ + return self.send( + from_session=from_session, + to_session=to_message.from_session, + type=MessageType.RESPONSE, + subject=f"Re: {to_message.subject}", + body=body, + data=data, + in_reply_to=to_message.message_id, + ) + + def ping_session(self, from_session: str, to_session: str) -> Message: + """ + Ping another session. + + Args: + from_session: Sender session ID + to_session: Target session ID + + Returns: + The ping Message object + """ + return self.send( + from_session=from_session, + to_session=to_session, + type=MessageType.PING, + subject="Ping", + body="Are you there?", + ) + + def get_messages( + self, + session_id: str, + include_broadcasts: bool = True, + message_type: Optional[MessageType] = None, + unread_only: bool = False, + ) -> List[Message]: + """ + Get messages for a session. + + Args: + session_id: Session to get messages for + include_broadcasts: Include broadcast messages + message_type: Filter by message type + unread_only: Only unread messages (not implemented yet) + + Returns: + List of messages + """ + messages = [ + msg for msg in self._messages + if msg.to_session == session_id or + (include_broadcasts and msg.to_session is None) + ] + + if message_type: + messages = [msg for msg in messages if msg.type == message_type] + + return messages + + def get_conversation(self, message_id: str) -> List[Message]: + """ + Get full conversation thread for a message. + + Args: + message_id: Starting message ID + + Returns: + List of messages in thread + """ + # Find the root message + root = next((m for m in self._messages if m.message_id == message_id), None) + if not root: + return [] + + # Find all messages in reply chain + thread = [root] + + # Walk up to find root + current = root + while current.in_reply_to: + parent = next((m for m in self._messages if m.message_id == current.in_reply_to), None) + if parent: + thread.insert(0, parent) + current = parent + else: + break + + # Walk down to find replies + def find_replies(msg_id: str): + replies = [m for m in self._messages if m.in_reply_to == msg_id] + for reply in replies: + thread.append(reply) + find_replies(reply.message_id) + + find_replies(thread[-1].message_id) + + return thread + + def register_handler(self, message_type: MessageType, handler: Callable): + """ + Register a handler for message type. + + Args: + message_type: Type to handle + handler: Handler function (receives Message) + """ + if message_type not in self._message_handlers: + self._message_handlers[message_type] = [] + + self._message_handlers[message_type].append(handler) + + def _trigger_handlers(self, message: Message): + """Trigger handlers for a message.""" + handlers = self._message_handlers.get(message.type, []) + + for handler in handlers: + try: + handler(message) + except Exception as e: + print(f"Warning: Message handler failed: {e}") + + def get_stats(self) -> Dict[str, Any]: + """Get collaboration statistics.""" + return { + 'total_messages': len(self._messages), + 'by_type': { + msg_type.value: len([m for m in self._messages if m.type == msg_type]) + for msg_type in MessageType + }, + 'active_sessions': len(self.registry.list_sessions()), + } diff --git a/prototypes/sessions/sessions/memory.py b/prototypes/sessions/sessions/memory.py new file mode 100644 index 0000000..39329af --- /dev/null +++ b/prototypes/sessions/sessions/memory.py @@ -0,0 +1,469 @@ +""" +Shared Memory - Cross-session memory space. + +Provides a shared memory space where sessions can store and retrieve +data, enabling state sharing and coordination across multiple sessions. +""" + +import json +from pathlib import Path +from dataclasses import dataclass, asdict +from datetime import datetime +from typing import Dict, List, Optional, Any +from enum import Enum + + +class MemoryType(Enum): + """Types of memory entries.""" + STATE = "state" # Session state + FACT = "fact" # Learned fact + DECISION = "decision" # Decision made + TASK = "task" # Task info + CONTEXT = "context" # Context/background + NOTE = "note" # General note + CONFIG = "config" # Configuration + + +@dataclass +class MemoryEntry: + """A shared memory entry.""" + + entry_id: str + type: MemoryType + key: str # Memory key (e.g., "current_task", "last_decision") + value: Any + session_id: str # Session that created this + timestamp: str + ttl: Optional[int] = None # Time to live in seconds (None = forever) + tags: List[str] = None + metadata: Dict[str, Any] = None + + def __post_init__(self): + """Initialize defaults.""" + if not self.timestamp: + self.timestamp = datetime.utcnow().isoformat() + if self.tags is None: + self.tags = [] + if self.metadata is None: + self.metadata = {} + + def is_expired(self) -> bool: + """Check if entry is expired.""" + if not self.ttl: + return False + + created = datetime.fromisoformat(self.timestamp.replace('Z', '+00:00')) + age_seconds = (datetime.utcnow() - created).total_seconds() + + return age_seconds > self.ttl + + def to_dict(self) -> Dict[str, Any]: + """Convert to dictionary.""" + data = asdict(self) + data['type'] = self.type.value + return data + + @classmethod + def from_dict(cls, data: Dict[str, Any]) -> 'MemoryEntry': + """Create from dictionary.""" + if 'type' in data and isinstance(data['type'], str): + data['type'] = MemoryType(data['type']) + return cls(**data) + + +class SharedMemory: + """ + Shared memory space for cross-session coordination. + + Provides a key-value store where sessions can store and retrieve + information, enabling state sharing and collaboration. + + Usage: + memory = SharedMemory() + + # Store a value + memory.set( + session_id="session-1", + key="current_task", + value="Building collaboration system", + type=MemoryType.STATE + ) + + # Get a value + value = memory.get("current_task") + + # Get all values for a key pattern + tasks = memory.search(key_pattern="task_*") + + # Get all entries from a session + entries = memory.get_by_session("session-1") + + # Query with tags + entries = memory.get_by_tags(["python", "code-review"]) + """ + + def __init__(self, memory_path: Optional[Path] = None): + """ + Initialize shared memory. + + Args: + memory_path: Path to store memory data + """ + if memory_path: + self.memory_path = Path(memory_path) + else: + # Default to bridge directory + bridge_root = Path(__file__).parent.parent.parent.parent + self.memory_path = bridge_root / ".sessions" / "shared_memory" + + self.memory_path.mkdir(parents=True, exist_ok=True) + self.memory_file = self.memory_path / "memory.json" + + self._entries: Dict[str, MemoryEntry] = {} + self._index_by_key: Dict[str, List[str]] = {} + self._index_by_session: Dict[str, List[str]] = {} + self._index_by_tag: Dict[str, List[str]] = {} + + self._load() + + def _load(self): + """Load memory from disk.""" + if not self.memory_file.exists(): + return + + try: + with open(self.memory_file, 'r') as f: + data = json.load(f) + + self._entries = { + eid: MemoryEntry.from_dict(edata) + for eid, edata in data.get('entries', {}).items() + } + + self._rebuild_indices() + self._cleanup_expired() + + except Exception as e: + print(f"Warning: Could not load shared memory: {e}") + + def _save(self): + """Save memory to disk.""" + try: + data = { + 'entries': { + eid: entry.to_dict() + for eid, entry in self._entries.items() + }, + 'updated_at': datetime.utcnow().isoformat(), + } + + with open(self.memory_file, 'w') as f: + json.dump(data, f, indent=2) + + except Exception as e: + print(f"Warning: Could not save shared memory: {e}") + + def _rebuild_indices(self): + """Rebuild all indices.""" + self._index_by_key.clear() + self._index_by_session.clear() + self._index_by_tag.clear() + + for entry_id, entry in self._entries.items(): + # Index by key + if entry.key not in self._index_by_key: + self._index_by_key[entry.key] = [] + self._index_by_key[entry.key].append(entry_id) + + # Index by session + if entry.session_id not in self._index_by_session: + self._index_by_session[entry.session_id] = [] + self._index_by_session[entry.session_id].append(entry_id) + + # Index by tags + for tag in entry.tags: + if tag not in self._index_by_tag: + self._index_by_tag[tag] = [] + self._index_by_tag[tag].append(entry_id) + + def _cleanup_expired(self): + """Remove expired entries.""" + expired = [ + eid for eid, entry in self._entries.items() + if entry.is_expired() + ] + + for eid in expired: + del self._entries[eid] + + if expired: + self._rebuild_indices() + + def set( + self, + session_id: str, + key: str, + value: Any, + type: MemoryType = MemoryType.STATE, + ttl: Optional[int] = None, + tags: Optional[List[str]] = None, + metadata: Optional[Dict[str, Any]] = None, + ) -> MemoryEntry: + """ + Store a value in shared memory. + + Args: + session_id: Session storing the value + key: Memory key + value: Value to store + type: Type of memory entry + ttl: Time to live in seconds (None = forever) + tags: Tags for searching + metadata: Additional metadata + + Returns: + The created MemoryEntry + """ + import uuid + + entry = MemoryEntry( + entry_id=str(uuid.uuid4()), + type=type, + key=key, + value=value, + session_id=session_id, + timestamp=datetime.utcnow().isoformat(), + ttl=ttl, + tags=tags or [], + metadata=metadata or {}, + ) + + self._entries[entry.entry_id] = entry + + # Update indices + if key not in self._index_by_key: + self._index_by_key[key] = [] + self._index_by_key[key].append(entry.entry_id) + + if session_id not in self._index_by_session: + self._index_by_session[session_id] = [] + self._index_by_session[session_id].append(entry.entry_id) + + for tag in entry.tags: + if tag not in self._index_by_tag: + self._index_by_tag[tag] = [] + self._index_by_tag[tag].append(entry.entry_id) + + self._save() + + return entry + + def get(self, key: str, default: Any = None) -> Any: + """ + Get the most recent value for a key. + + Args: + key: Memory key + default: Default if not found + + Returns: + The value or default + """ + self._cleanup_expired() + + entry_ids = self._index_by_key.get(key, []) + + if not entry_ids: + return default + + # Get most recent entry + entries = [self._entries[eid] for eid in entry_ids if eid in self._entries] + + if not entries: + return default + + entries.sort(key=lambda e: e.timestamp, reverse=True) + + return entries[0].value + + def get_entry(self, key: str) -> Optional[MemoryEntry]: + """ + Get the most recent entry for a key. + + Args: + key: Memory key + + Returns: + The MemoryEntry or None + """ + self._cleanup_expired() + + entry_ids = self._index_by_key.get(key, []) + + if not entry_ids: + return None + + # Get most recent entry + entries = [self._entries[eid] for eid in entry_ids if eid in self._entries] + + if not entries: + return None + + entries.sort(key=lambda e: e.timestamp, reverse=True) + + return entries[0] + + def get_all(self, key: str) -> List[MemoryEntry]: + """ + Get all entries for a key. + + Args: + key: Memory key + + Returns: + List of MemoryEntry objects + """ + self._cleanup_expired() + + entry_ids = self._index_by_key.get(key, []) + entries = [self._entries[eid] for eid in entry_ids if eid in self._entries] + entries.sort(key=lambda e: e.timestamp, reverse=True) + + return entries + + def get_by_session(self, session_id: str) -> List[MemoryEntry]: + """ + Get all entries from a session. + + Args: + session_id: Session ID + + Returns: + List of MemoryEntry objects + """ + self._cleanup_expired() + + entry_ids = self._index_by_session.get(session_id, []) + entries = [self._entries[eid] for eid in entry_ids if eid in self._entries] + entries.sort(key=lambda e: e.timestamp, reverse=True) + + return entries + + def get_by_tags(self, tags: List[str], match_all: bool = False) -> List[MemoryEntry]: + """ + Get entries by tags. + + Args: + tags: Tags to search for + match_all: If True, entry must have all tags; if False, any tag + + Returns: + List of MemoryEntry objects + """ + self._cleanup_expired() + + if not tags: + return [] + + # Get entry IDs for each tag + entry_sets = [set(self._index_by_tag.get(tag, [])) for tag in tags] + + if match_all: + # Intersection - must have all tags + entry_ids = set.intersection(*entry_sets) if entry_sets else set() + else: + # Union - any tag + entry_ids = set.union(*entry_sets) if entry_sets else set() + + entries = [self._entries[eid] for eid in entry_ids if eid in self._entries] + entries.sort(key=lambda e: e.timestamp, reverse=True) + + return entries + + def search(self, key_pattern: str) -> List[MemoryEntry]: + """ + Search entries by key pattern. + + Args: + key_pattern: Pattern to match (supports * wildcard) + + Returns: + List of matching MemoryEntry objects + """ + self._cleanup_expired() + + import fnmatch + + matching_keys = [ + key for key in self._index_by_key.keys() + if fnmatch.fnmatch(key, key_pattern) + ] + + entries = [] + for key in matching_keys: + entries.extend(self.get_all(key)) + + entries.sort(key=lambda e: e.timestamp, reverse=True) + + return entries + + def delete(self, key: str, session_id: Optional[str] = None) -> int: + """ + Delete entries for a key. + + Args: + key: Memory key + session_id: Only delete from this session (optional) + + Returns: + Number of entries deleted + """ + entry_ids = self._index_by_key.get(key, []) + + to_delete = [] + for eid in entry_ids: + if eid in self._entries: + if session_id is None or self._entries[eid].session_id == session_id: + to_delete.append(eid) + + for eid in to_delete: + del self._entries[eid] + + if to_delete: + self._rebuild_indices() + self._save() + + return len(to_delete) + + def clear(self, session_id: Optional[str] = None): + """ + Clear memory entries. + + Args: + session_id: Only clear from this session (optional) + """ + if session_id: + entry_ids = self._index_by_session.get(session_id, []) + for eid in entry_ids: + if eid in self._entries: + del self._entries[eid] + else: + self._entries.clear() + + self._rebuild_indices() + self._save() + + def get_stats(self) -> Dict[str, Any]: + """Get memory statistics.""" + self._cleanup_expired() + + return { + 'total_entries': len(self._entries), + 'by_type': { + mem_type.value: len([e for e in self._entries.values() if e.type == mem_type]) + for mem_type in MemoryType + }, + 'unique_keys': len(self._index_by_key), + 'unique_sessions': len(self._index_by_session), + 'unique_tags': len(self._index_by_tag), + } diff --git a/prototypes/sessions/sessions/registry.py b/prototypes/sessions/sessions/registry.py new file mode 100644 index 0000000..cf144b1 --- /dev/null +++ b/prototypes/sessions/sessions/registry.py @@ -0,0 +1,368 @@ +""" +Session Registry - Track active sessions in the mesh. + +Maintains a registry of all active AI/agent sessions with their metadata, +status, and capabilities. Enables session discovery and coordination. +""" + +import os +import json +import time +from pathlib import Path +from dataclasses import dataclass, field, asdict +from datetime import datetime, timedelta +from typing import Dict, List, Optional, Any +from enum import Enum + + +class SessionStatus(Enum): + """Session status states.""" + ACTIVE = "active" + IDLE = "idle" + WORKING = "working" + WAITING = "waiting" + OFFLINE = "offline" + + +@dataclass +class Session: + """Represents an active session in the mesh.""" + + session_id: str + agent_name: str # e.g., "Cece", "Agent-1" + agent_type: str # e.g., "Claude", "GPT-4", "Custom" + status: SessionStatus = SessionStatus.ACTIVE + started_at: str = "" + last_ping: str = "" + human_user: Optional[str] = None # e.g., "Alexa" + location: str = "BlackRoad-OS/.github" + capabilities: List[str] = field(default_factory=list) + current_task: Optional[str] = None + metadata: Dict[str, Any] = field(default_factory=dict) + + def __post_init__(self): + """Initialize timestamps if not provided.""" + if not self.started_at: + self.started_at = datetime.utcnow().isoformat() + if not self.last_ping: + self.last_ping = datetime.utcnow().isoformat() + + def ping(self): + """Update last ping timestamp.""" + self.last_ping = datetime.utcnow().isoformat() + + def is_stale(self, timeout_seconds: int = 300) -> bool: + """Check if session is stale (no ping in timeout period).""" + last_ping_dt = datetime.fromisoformat(self.last_ping.replace('Z', '+00:00')) + return datetime.utcnow() - last_ping_dt > timedelta(seconds=timeout_seconds) + + def to_dict(self) -> Dict[str, Any]: + """Convert to dictionary.""" + data = asdict(self) + data['status'] = self.status.value + return data + + @classmethod + def from_dict(cls, data: Dict[str, Any]) -> 'Session': + """Create from dictionary.""" + if 'status' in data and isinstance(data['status'], str): + data['status'] = SessionStatus(data['status']) + return cls(**data) + + +class SessionRegistry: + """ + Registry of active sessions. + + Tracks all AI/agent sessions currently active in the mesh, + enabling discovery, collaboration, and coordination. + + Usage: + registry = SessionRegistry() + + # Register a new session + session = registry.register( + session_id="cece-001", + agent_name="Cece", + agent_type="Claude", + human_user="Alexa" + ) + + # List all sessions + sessions = registry.list_sessions() + + # Ping to keep alive + registry.ping("cece-001") + + # Update status + registry.update_status("cece-001", SessionStatus.WORKING) + + # Find sessions by criteria + active = registry.find_sessions(status=SessionStatus.ACTIVE) + """ + + def __init__(self, registry_path: Optional[Path] = None): + """ + Initialize the session registry. + + Args: + registry_path: Path to store registry data (auto-detected if None) + """ + if registry_path: + self.registry_path = Path(registry_path) + else: + # Default to bridge directory + bridge_root = Path(__file__).parent.parent.parent.parent + self.registry_path = bridge_root / ".sessions" + + self.registry_path.mkdir(exist_ok=True) + self.sessions_file = self.registry_path / "active_sessions.json" + + self._sessions: Dict[str, Session] = {} + self._load() + + def _load(self): + """Load sessions from disk.""" + if not self.sessions_file.exists(): + return + + try: + with open(self.sessions_file, 'r') as f: + data = json.load(f) + + self._sessions = { + sid: Session.from_dict(sdata) + for sid, sdata in data.get('sessions', {}).items() + } + + # Clean up stale sessions + self._cleanup_stale() + + except Exception as e: + print(f"Warning: Could not load session registry: {e}") + + def _save(self): + """Save sessions to disk.""" + try: + data = { + 'sessions': { + sid: session.to_dict() + for sid, session in self._sessions.items() + }, + 'updated_at': datetime.utcnow().isoformat(), + } + + with open(self.sessions_file, 'w') as f: + json.dump(data, f, indent=2) + + except Exception as e: + print(f"Warning: Could not save session registry: {e}") + + def _cleanup_stale(self, timeout_seconds: int = 300): + """Remove stale sessions.""" + stale = [ + sid for sid, session in self._sessions.items() + if session.is_stale(timeout_seconds) + ] + + for sid in stale: + self._sessions[sid].status = SessionStatus.OFFLINE + # Don't delete, just mark offline for historical tracking + + def register( + self, + session_id: str, + agent_name: str, + agent_type: str, + human_user: Optional[str] = None, + capabilities: Optional[List[str]] = None, + metadata: Optional[Dict[str, Any]] = None, + ) -> Session: + """ + Register a new session. + + Args: + session_id: Unique session identifier + agent_name: Name of the agent (e.g., "Cece") + agent_type: Type of agent (e.g., "Claude", "GPT-4") + human_user: Human user associated with session + capabilities: List of capabilities this session supports + metadata: Additional metadata + + Returns: + The registered Session object + """ + session = Session( + session_id=session_id, + agent_name=agent_name, + agent_type=agent_type, + human_user=human_user, + capabilities=capabilities or [], + metadata=metadata or {}, + ) + + self._sessions[session_id] = session + self._save() + + return session + + def unregister(self, session_id: str) -> bool: + """ + Unregister a session. + + Args: + session_id: Session to unregister + + Returns: + True if unregistered, False if not found + """ + if session_id in self._sessions: + self._sessions[session_id].status = SessionStatus.OFFLINE + self._save() + return True + return False + + def ping(self, session_id: str) -> bool: + """ + Ping a session to keep it alive. + + Args: + session_id: Session to ping + + Returns: + True if pinged, False if not found + """ + if session_id in self._sessions: + self._sessions[session_id].ping() + self._save() + return True + return False + + def get(self, session_id: str) -> Optional[Session]: + """Get a session by ID.""" + return self._sessions.get(session_id) + + def update_status( + self, + session_id: str, + status: SessionStatus, + current_task: Optional[str] = None + ) -> bool: + """ + Update session status. + + Args: + session_id: Session to update + status: New status + current_task: Current task description + + Returns: + True if updated, False if not found + """ + if session_id in self._sessions: + self._sessions[session_id].status = status + if current_task is not None: + self._sessions[session_id].current_task = current_task + self._sessions[session_id].ping() + self._save() + return True + return False + + def list_sessions( + self, + include_offline: bool = False + ) -> List[Session]: + """ + List all sessions. + + Args: + include_offline: Include offline sessions + + Returns: + List of Session objects + """ + self._cleanup_stale() + + sessions = list(self._sessions.values()) + + if not include_offline: + sessions = [s for s in sessions if s.status != SessionStatus.OFFLINE] + + return sessions + + def find_sessions( + self, + status: Optional[SessionStatus] = None, + agent_type: Optional[str] = None, + human_user: Optional[str] = None, + capability: Optional[str] = None, + ) -> List[Session]: + """ + Find sessions matching criteria. + + Args: + status: Filter by status + agent_type: Filter by agent type + human_user: Filter by human user + capability: Filter by capability + + Returns: + List of matching sessions + """ + self._cleanup_stale() + + results = list(self._sessions.values()) + + if status: + results = [s for s in results if s.status == status] + + if agent_type: + results = [s for s in results if s.agent_type == agent_type] + + if human_user: + results = [s for s in results if s.human_user == human_user] + + if capability: + results = [s for s in results if capability in s.capabilities] + + return results + + def get_stats(self) -> Dict[str, Any]: + """Get registry statistics.""" + self._cleanup_stale() + + all_sessions = list(self._sessions.values()) + active = [s for s in all_sessions if s.status != SessionStatus.OFFLINE] + + stats = { + 'total_sessions': len(self._sessions), + 'active_sessions': len(active), + 'by_status': {}, + 'by_agent_type': {}, + 'by_user': {}, + } + + for session in all_sessions: + # Count by status + status_key = session.status.value + stats['by_status'][status_key] = stats['by_status'].get(status_key, 0) + 1 + + # Count by agent type + stats['by_agent_type'][session.agent_type] = \ + stats['by_agent_type'].get(session.agent_type, 0) + 1 + + # Count by user + if session.human_user: + stats['by_user'][session.human_user] = \ + stats['by_user'].get(session.human_user, 0) + 1 + + return stats + + def clear_offline(self): + """Remove offline sessions from registry.""" + self._sessions = { + sid: session + for sid, session in self._sessions.items() + if session.status != SessionStatus.OFFLINE + } + self._save() diff --git a/prototypes/sessions/setup.py b/prototypes/sessions/setup.py new file mode 100644 index 0000000..8595651 --- /dev/null +++ b/prototypes/sessions/setup.py @@ -0,0 +1,26 @@ +from setuptools import setup, find_packages + +setup( + name="blackroad-sessions", + version="0.1.0", + description="Session management, collaboration, and shared memory for BlackRoad mesh", + author="BlackRoad OS", + packages=find_packages(), + python_requires=">=3.8", + install_requires=[ + # No external dependencies - uses only stdlib + ], + entry_points={ + "console_scripts": [ + "blackroad-sessions=sessions.cli:main", + ], + }, + classifiers=[ + "Development Status :: 3 - Alpha", + "Intended Audience :: Developers", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + ], +) diff --git a/prototypes/sessions/test_sessions.py b/prototypes/sessions/test_sessions.py new file mode 100644 index 0000000..eaf0a62 --- /dev/null +++ b/prototypes/sessions/test_sessions.py @@ -0,0 +1,307 @@ +""" +Test suite for BlackRoad Session Management. +""" + +import sys +import os +import json +import tempfile +import shutil +from pathlib import Path + +# Add parent to path +sys.path.insert(0, str(Path(__file__).parent.parent)) + +from sessions.registry import SessionRegistry, SessionStatus +from sessions.collaboration import CollaborationHub, MessageType +from sessions.memory import SharedMemory, MemoryType + + +def test_session_registry(): + """Test session registry functionality.""" + print("\nπŸ§ͺ Testing Session Registry...") + + # Use temp directory + temp_dir = Path(tempfile.mkdtemp()) + + try: + registry = SessionRegistry(temp_dir) + + # Register sessions + session1 = registry.register("test-1", "Test Agent 1", "Claude", "Tester") + assert session1.session_id == "test-1" + assert session1.agent_name == "Test Agent 1" + print(" βœ… Session registration") + + session2 = registry.register("test-2", "Test Agent 2", "GPT-4", "Tester", + capabilities=["python", "review"]) + assert "python" in session2.capabilities + print(" βœ… Session with capabilities") + + # List sessions + sessions = registry.list_sessions() + assert len(sessions) == 2 + print(" βœ… List sessions") + + # Ping + assert registry.ping("test-1") + print(" βœ… Ping session") + + # Update status + assert registry.update_status("test-1", SessionStatus.WORKING, "Testing") + session = registry.get("test-1") + assert session.status == SessionStatus.WORKING + assert session.current_task == "Testing" + print(" βœ… Update status") + + # Find sessions + working = registry.find_sessions(status=SessionStatus.WORKING) + assert len(working) == 1 + print(" βœ… Find by status") + + python_sessions = registry.find_sessions(capability="python") + assert len(python_sessions) == 1 + print(" βœ… Find by capability") + + # Stats + stats = registry.get_stats() + assert stats['total_sessions'] == 2 + assert stats['active_sessions'] >= 1 + print(" βœ… Statistics") + + print("βœ… Session Registry tests passed!") + + finally: + shutil.rmtree(temp_dir) + + +def test_collaboration_hub(): + """Test collaboration hub functionality.""" + print("\nπŸ§ͺ Testing Collaboration Hub...") + + # Use temp directory + temp_dir = Path(tempfile.mkdtemp()) + + try: + registry = SessionRegistry(temp_dir) + hub = CollaborationHub(registry, temp_dir / "messages") + + # Register sessions + registry.register("session-1", "Agent 1", "Claude", "Tester") + registry.register("session-2", "Agent 2", "GPT-4", "Tester") + + # Send message + msg = hub.send( + "session-1", "session-2", + MessageType.REQUEST, + "Test message", + "This is a test" + ) + assert msg.from_session == "session-1" + assert msg.to_session == "session-2" + print(" βœ… Send message") + + # Broadcast + broadcast = hub.broadcast("session-1", "Announcement", "Test broadcast") + assert broadcast.to_session is None + print(" βœ… Broadcast") + + # Reply + reply = hub.reply("session-2", msg, "Got it!") + assert reply.in_reply_to == msg.message_id + print(" βœ… Reply") + + # Ping + ping = hub.ping_session("session-1", "session-2") + assert ping.type == MessageType.PING + print(" βœ… Ping") + + # Get messages + messages = hub.get_messages("session-2") + assert len(messages) >= 2 # Direct message + broadcast + print(" βœ… Get messages") + + # Get conversation + thread = hub.get_conversation(msg.message_id) + assert len(thread) == 2 # Original + reply + print(" βœ… Get conversation") + + # Stats + stats = hub.get_stats() + assert stats['total_messages'] >= 4 + print(" βœ… Statistics") + + print("βœ… Collaboration Hub tests passed!") + + finally: + shutil.rmtree(temp_dir) + + +def test_shared_memory(): + """Test shared memory functionality.""" + print("\nπŸ§ͺ Testing Shared Memory...") + + # Use temp directory + temp_dir = Path(tempfile.mkdtemp()) + + try: + memory = SharedMemory(temp_dir) + + # Set value + entry = memory.set( + "session-1", + "test_key", + "test_value", + type=MemoryType.STATE, + tags=["test", "example"] + ) + assert entry.key == "test_key" + print(" βœ… Set value") + + # Get value + value = memory.get("test_key") + assert value == "test_value" + print(" βœ… Get value") + + # Set another value for same key + memory.set("session-2", "test_key", "newer_value") + + # Get most recent + value = memory.get("test_key") + assert value == "newer_value" + print(" βœ… Get most recent") + + # Get all + all_entries = memory.get_all("test_key") + assert len(all_entries) == 2 + print(" βœ… Get all entries") + + # Get by session + session_entries = memory.get_by_session("session-1") + assert len(session_entries) == 1 + print(" βœ… Get by session") + + # Get by tags + tagged = memory.get_by_tags(["test"]) + assert len(tagged) >= 1 + print(" βœ… Get by tags") + + # Search pattern + memory.set("session-1", "task_1", "Task 1") + memory.set("session-1", "task_2", "Task 2") + tasks = memory.search("task_*") + assert len(tasks) == 2 + print(" βœ… Search pattern") + + # Delete + deleted = memory.delete("test_key") + assert deleted == 2 + value = memory.get("test_key") + assert value is None + print(" βœ… Delete") + + # Stats + stats = memory.get_stats() + assert stats['total_entries'] >= 2 + print(" βœ… Statistics") + + print("βœ… Shared Memory tests passed!") + + finally: + shutil.rmtree(temp_dir) + + +def test_integration(): + """Test integrated workflow.""" + print("\nπŸ§ͺ Testing Integration...") + + # Use temp directory + temp_dir = Path(tempfile.mkdtemp()) + + try: + registry = SessionRegistry(temp_dir) + hub = CollaborationHub(registry, temp_dir / "messages") + memory = SharedMemory(temp_dir / "memory") + + # Register sessions + registry.register("planner", "Planner", "Claude", "Tester") + registry.register("developer", "Developer", "GPT-4", "Tester") + + # Planner creates a plan + memory.set("planner", "project_plan", { + "phase": "design", + "tasks": ["api", "database", "frontend"] + }, type=MemoryType.STATE, tags=["project"]) + print(" βœ… Planner stored plan") + + # Developer reads plan + plan = memory.get("project_plan") + assert plan["phase"] == "design" + print(" βœ… Developer read plan") + + # Developer asks question + msg = hub.send( + "developer", "planner", + MessageType.REQUEST, + "API design", + "Should we use REST or GraphQL?" + ) + print(" βœ… Developer sent question") + + # Planner responds + reply = hub.reply("planner", msg, "Let's go with REST for now") + print(" βœ… Planner replied") + + # Get conversation + thread = hub.get_conversation(msg.message_id) + assert len(thread) == 2 + print(" βœ… Retrieved conversation") + + # Developer updates status + registry.update_status("developer", SessionStatus.WORKING, "Building API") + print(" βœ… Developer updated status") + + # Get stats + session_stats = registry.get_stats() + collab_stats = hub.get_stats() + memory_stats = memory.get_stats() + + assert session_stats['total_sessions'] == 2 + assert collab_stats['total_messages'] >= 2 + assert memory_stats['total_entries'] >= 1 + print(" βœ… All stats available") + + print("βœ… Integration tests passed!") + + finally: + shutil.rmtree(temp_dir) + + +def run_all_tests(): + """Run all tests.""" + print("\n" + "="*60) + print("πŸš€ BlackRoad Session Management Test Suite") + print("="*60) + + try: + test_session_registry() + test_collaboration_hub() + test_shared_memory() + test_integration() + + print("\n" + "="*60) + print("βœ… ALL TESTS PASSED!") + print("="*60) + + return True + + except Exception as e: + print(f"\n❌ TEST FAILED: {e}") + import traceback + traceback.print_exc() + return False + + +if __name__ == "__main__": + success = run_all_tests() + sys.exit(0 if success else 1) From 3bed7181b7eff33678951673b8503db0c6b8bb76 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 27 Jan 2026 21:11:20 +0000 Subject: [PATCH 3/3] Integrate sessions with MCP server and add demo Co-authored-by: blackboxprogramming <118287761+blackboxprogramming@users.noreply.github.com> --- .sessions/active_sessions.json | 87 ++++ .sessions/messages/recent_messages.json | 104 ++++ .sessions/shared_memory/memory.json | 104 ++++ prototypes/mcp-server/blackroad_mcp/server.py | 463 ++++++++++++++++++ prototypes/sessions/demo.py | 347 +++++++++++++ prototypes/sessions/sessions/__init__.py | 3 +- prototypes/sessions/update_status.py | 110 +++++ 7 files changed, 1217 insertions(+), 1 deletion(-) create mode 100644 .sessions/active_sessions.json create mode 100644 .sessions/messages/recent_messages.json create mode 100644 .sessions/shared_memory/memory.json create mode 100644 prototypes/sessions/demo.py create mode 100644 prototypes/sessions/update_status.py diff --git a/.sessions/active_sessions.json b/.sessions/active_sessions.json new file mode 100644 index 0000000..ccce80c --- /dev/null +++ b/.sessions/active_sessions.json @@ -0,0 +1,87 @@ +{ + "sessions": { + "cece-001": { + "session_id": "cece-001", + "agent_name": "Cece", + "agent_type": "Claude", + "status": "working", + "started_at": "2026-01-27T21:11:11.490417", + "last_ping": "2026-01-27T21:11:11.490810", + "human_user": "Alexa", + "location": "BlackRoad-OS/.github", + "capabilities": [ + "python", + "planning", + "review" + ], + "current_task": "Building collaboration system", + "metadata": {} + }, + "agent-002": { + "session_id": "agent-002", + "agent_name": "Agent-2", + "agent_type": "GPT-4", + "status": "active", + "started_at": "2026-01-27T21:11:11.490568", + "last_ping": "2026-01-27T21:11:11.490572", + "human_user": "Alexa", + "location": "BlackRoad-OS/.github", + "capabilities": [ + "javascript", + "react", + "testing" + ], + "current_task": null, + "metadata": {} + }, + "planner-001": { + "session_id": "planner-001", + "agent_name": "Planner", + "agent_type": "Claude", + "status": "active", + "started_at": "2026-01-27T21:11:11.493594", + "last_ping": "2026-01-27T21:11:11.493598", + "human_user": "Alexa", + "location": "BlackRoad-OS/.github", + "capabilities": [ + "planning", + "architecture" + ], + "current_task": null, + "metadata": {} + }, + "developer-001": { + "session_id": "developer-001", + "agent_name": "Developer", + "agent_type": "GPT-4", + "status": "working", + "started_at": "2026-01-27T21:11:11.493823", + "last_ping": "2026-01-27T21:11:11.494830", + "human_user": "Alexa", + "location": "BlackRoad-OS/.github", + "capabilities": [ + "python", + "coding" + ], + "current_task": "Implementing user-auth", + "metadata": {} + }, + "reviewer-001": { + "session_id": "reviewer-001", + "agent_name": "Reviewer", + "agent_type": "Claude", + "status": "working", + "started_at": "2026-01-27T21:11:11.494076", + "last_ping": "2026-01-27T21:11:11.495787", + "human_user": "Alexa", + "location": "BlackRoad-OS/.github", + "capabilities": [ + "review", + "security" + ], + "current_task": "Reviewing auth.py", + "metadata": {} + } + }, + "updated_at": "2026-01-27T21:11:11.495835" +} \ No newline at end of file diff --git a/.sessions/messages/recent_messages.json b/.sessions/messages/recent_messages.json new file mode 100644 index 0000000..bd8c77d --- /dev/null +++ b/.sessions/messages/recent_messages.json @@ -0,0 +1,104 @@ +{ + "messages": [ + { + "message_id": "118b89b5-db52-45c3-bbc8-1b1a612ed98e", + "type": "ping", + "from_session": "cece-001", + "to_session": "agent-002", + "subject": "Ping", + "body": "Are you there?", + "data": {}, + "timestamp": "2026-01-27T21:11:11.491442", + "in_reply_to": null + }, + { + "message_id": "8512000c-6978-4653-ad2e-5156acd7f827", + "type": "request", + "from_session": "cece-001", + "to_session": "agent-002", + "subject": "React component review", + "body": "Can you review this React component for me?", + "data": { + "component": "UserProfile.jsx", + "lines": 150 + }, + "timestamp": "2026-01-27T21:11:11.491598", + "in_reply_to": null + }, + { + "message_id": "09715a4f-5515-40b2-9bff-f3a1af5133f6", + "type": "response", + "from_session": "agent-002", + "to_session": "cece-001", + "subject": "Re: React component review", + "body": "Sure! I'll review it now. Looks good overall, minor suggestions in comments.", + "data": { + "approved": true, + "suggestions": 3 + }, + "timestamp": "2026-01-27T21:11:11.491916", + "in_reply_to": "8512000c-6978-4653-ad2e-5156acd7f827" + }, + { + "message_id": "0d4b48b4-baef-418a-95f0-8ded00a3a4d5", + "type": "broadcast", + "from_session": "cece-001", + "to_session": null, + "subject": "Deployment scheduled", + "body": "Production deployment scheduled for 2PM", + "data": {}, + "timestamp": "2026-01-27T21:11:11.492219", + "in_reply_to": null + }, + { + "message_id": "ae1af220-52c7-405e-9a7f-513c1d3a75da", + "type": "broadcast", + "from_session": "planner-001", + "to_session": null, + "subject": "Plan ready", + "body": "Implementation plan for user-auth is ready", + "data": {}, + "timestamp": "2026-01-27T21:11:11.494572", + "in_reply_to": null + }, + { + "message_id": "6cbcff50-bfac-4abc-be8d-aac39f58d5c6", + "type": "task_offer", + "from_session": "developer-001", + "to_session": "reviewer-001", + "subject": "Code review needed", + "body": "User auth implementation ready for review", + "data": { + "file": "auth.py", + "priority": "high" + }, + "timestamp": "2026-01-27T21:11:11.495283", + "in_reply_to": null + }, + { + "message_id": "a905a0f8-1d5e-40e3-a675-4b59755bccef", + "type": "task_accept", + "from_session": "reviewer-001", + "to_session": "developer-001", + "subject": "Starting review", + "body": "Will review the auth code now", + "data": {}, + "timestamp": "2026-01-27T21:11:11.495524", + "in_reply_to": null + }, + { + "message_id": "440db0cb-4a7a-4cc1-85b3-9955d82573cb", + "type": "response", + "from_session": "reviewer-001", + "to_session": "developer-001", + "subject": "Review complete", + "body": "Code looks great! LGTM with 2 minor suggestions.", + "data": { + "approved": true + }, + "timestamp": "2026-01-27T21:11:11.496875", + "in_reply_to": null + } + ], + "updated_at": "2026-01-27T21:11:11.496938" +} \ No newline at end of file diff --git a/.sessions/shared_memory/memory.json b/.sessions/shared_memory/memory.json new file mode 100644 index 0000000..ac85c55 --- /dev/null +++ b/.sessions/shared_memory/memory.json @@ -0,0 +1,104 @@ +{ + "entries": { + "c1e9fd53-77ac-4139-9d9b-1e9cca8c322f": { + "entry_id": "c1e9fd53-77ac-4139-9d9b-1e9cca8c322f", + "type": "state", + "key": "project_plan", + "value": { + "phase": "design", + "tasks": [ + "api-design", + "database-schema", + "frontend-mockups" + ], + "deadline": "2026-02-01" + }, + "session_id": "cece-001", + "timestamp": "2026-01-27T21:11:11.492758", + "ttl": null, + "tags": [ + "project", + "active", + "design" + ], + "metadata": {} + }, + "f628f9f0-2488-412d-8080-2142d48aae5a": { + "entry_id": "f628f9f0-2488-412d-8080-2142d48aae5a", + "type": "task", + "key": "task_api-design", + "value": { + "status": "completed", + "owner": "agent-002", + "completed_at": "2026-01-27" + }, + "session_id": "agent-002", + "timestamp": "2026-01-27T21:11:11.492905", + "ttl": null, + "tags": [ + "task", + "completed" + ], + "metadata": {} + }, + "9bedbbff-8385-4324-89ce-8a3dbd89772a": { + "entry_id": "9bedbbff-8385-4324-89ce-8a3dbd89772a", + "type": "state", + "key": "implementation_plan", + "value": { + "feature": "user-auth", + "steps": [ + "design", + "implement", + "test", + "review" + ] + }, + "session_id": "planner-001", + "timestamp": "2026-01-27T21:11:11.494364", + "ttl": null, + "tags": [ + "plan", + "auth" + ], + "metadata": {} + }, + "baac04e5-3913-459d-851d-fe03887f5605": { + "entry_id": "baac04e5-3913-459d-851d-fe03887f5605", + "type": "state", + "key": "code_user-auth", + "value": { + "file": "auth.py", + "lines": 250, + "tests": "passing" + }, + "session_id": "developer-001", + "timestamp": "2026-01-27T21:11:11.495052", + "ttl": null, + "tags": [ + "code", + "ready-for-review" + ], + "metadata": {} + }, + "5f0e106f-385c-4fdb-be39-ff23112215f3": { + "entry_id": "5f0e106f-385c-4fdb-be39-ff23112215f3", + "type": "decision", + "key": "review_user-auth", + "value": { + "status": "approved", + "issues": 0, + "suggestions": 2 + }, + "session_id": "reviewer-001", + "timestamp": "2026-01-27T21:11:11.496007", + "ttl": null, + "tags": [ + "review", + "approved" + ], + "metadata": {} + } + }, + "updated_at": "2026-01-27T21:11:11.496065" +} \ No newline at end of file diff --git a/prototypes/mcp-server/blackroad_mcp/server.py b/prototypes/mcp-server/blackroad_mcp/server.py index 7a4dfa7..d69fa14 100644 --- a/prototypes/mcp-server/blackroad_mcp/server.py +++ b/prototypes/mcp-server/blackroad_mcp/server.py @@ -17,6 +17,7 @@ sys.path.insert(0, str(PROTO_ROOT / "operator")) sys.path.insert(0, str(PROTO_ROOT / "dispatcher")) sys.path.insert(0, str(PROTO_ROOT / "webhooks")) +sys.path.insert(0, str(PROTO_ROOT / "sessions")) @dataclass @@ -67,6 +68,9 @@ def __init__(self): self._operator = None self._dispatcher = None self._webhook_receiver = None + self._session_registry = None + self._collaboration_hub = None + self._shared_memory = None self._signal_history: List[Dict[str, Any]] = [] # Define tools @@ -236,6 +240,213 @@ def _define_tools(self) -> List[Tool]: "required": ["node"] } ), + # Session Management Tools + Tool( + name="session_register", + description="Register a new session in the mesh for discovery and collaboration.", + input_schema={ + "type": "object", + "properties": { + "session_id": { + "type": "string", + "description": "Unique session identifier" + }, + "agent_name": { + "type": "string", + "description": "Agent name (e.g., 'Cece', 'Agent-1')" + }, + "agent_type": { + "type": "string", + "description": "Agent type (e.g., 'Claude', 'GPT-4')" + }, + "human_user": { + "type": "string", + "description": "Associated human user" + }, + "capabilities": { + "type": "array", + "items": {"type": "string"}, + "description": "Session capabilities" + } + }, + "required": ["session_id", "agent_name", "agent_type"] + } + ), + Tool( + name="session_list", + description="List all active sessions in the mesh.", + input_schema={ + "type": "object", + "properties": { + "include_offline": { + "type": "boolean", + "description": "Include offline sessions" + } + } + } + ), + Tool( + name="session_ping", + description="Ping a session to check if it's alive and send a collaborative ping message.", + input_schema={ + "type": "object", + "properties": { + "from_session": { + "type": "string", + "description": "Your session ID" + }, + "to_session": { + "type": "string", + "description": "Target session ID to ping" + } + }, + "required": ["from_session", "to_session"] + } + ), + Tool( + name="collab_send", + description="Send a collaboration message to another session.", + input_schema={ + "type": "object", + "properties": { + "from_session": { + "type": "string", + "description": "Your session ID" + }, + "to_session": { + "type": "string", + "description": "Target session ID" + }, + "message_type": { + "type": "string", + "enum": ["ping", "request", "response", "notification", "task_offer", "task_accept", "sync", "handoff"], + "description": "Type of message" + }, + "subject": { + "type": "string", + "description": "Message subject" + }, + "body": { + "type": "string", + "description": "Message body" + }, + "data": { + "type": "object", + "description": "Additional data", + "additionalProperties": True + } + }, + "required": ["from_session", "to_session", "subject", "body"] + } + ), + Tool( + name="collab_broadcast", + description="Broadcast a message to all active sessions.", + input_schema={ + "type": "object", + "properties": { + "from_session": { + "type": "string", + "description": "Your session ID" + }, + "subject": { + "type": "string", + "description": "Message subject" + }, + "body": { + "type": "string", + "description": "Message body" + } + }, + "required": ["from_session", "subject", "body"] + } + ), + Tool( + name="collab_get_messages", + description="Get collaboration messages for a session.", + input_schema={ + "type": "object", + "properties": { + "session_id": { + "type": "string", + "description": "Session ID to get messages for" + }, + "message_type": { + "type": "string", + "enum": ["ping", "request", "response", "broadcast", "notification", "task_offer", "task_accept", "sync", "handoff"], + "description": "Filter by message type" + } + }, + "required": ["session_id"] + } + ), + Tool( + name="memory_set", + description="Store a value in shared memory accessible by all sessions.", + input_schema={ + "type": "object", + "properties": { + "session_id": { + "type": "string", + "description": "Your session ID" + }, + "key": { + "type": "string", + "description": "Memory key" + }, + "value": { + "description": "Value to store (any type)" + }, + "memory_type": { + "type": "string", + "enum": ["state", "fact", "decision", "task", "context", "note", "config"], + "description": "Type of memory entry" + }, + "tags": { + "type": "array", + "items": {"type": "string"}, + "description": "Tags for searching" + } + }, + "required": ["session_id", "key", "value"] + } + ), + Tool( + name="memory_get", + description="Get a value from shared memory.", + input_schema={ + "type": "object", + "properties": { + "key": { + "type": "string", + "description": "Memory key" + } + }, + "required": ["key"] + } + ), + Tool( + name="memory_search", + description="Search shared memory by pattern or tags.", + input_schema={ + "type": "object", + "properties": { + "pattern": { + "type": "string", + "description": "Key pattern (supports * wildcard)" + }, + "tags": { + "type": "array", + "items": {"type": "string"}, + "description": "Tags to search for" + }, + "session_id": { + "type": "string", + "description": "Filter by session ID" + } + } + } + ), ] def _define_resources(self) -> List[Resource]: @@ -311,6 +522,39 @@ def webhook_receiver(self): print(f"Warning: Could not load WebhookReceiver: {e}", file=sys.stderr) return self._webhook_receiver + @property + def session_registry(self): + """Lazy load the Session Registry.""" + if self._session_registry is None: + try: + from sessions.registry import SessionRegistry + self._session_registry = SessionRegistry() + except ImportError as e: + print(f"Warning: Could not load SessionRegistry: {e}", file=sys.stderr) + return self._session_registry + + @property + def collaboration_hub(self): + """Lazy load the Collaboration Hub.""" + if self._collaboration_hub is None: + try: + from sessions.collaboration import CollaborationHub + self._collaboration_hub = CollaborationHub(self.session_registry) + except ImportError as e: + print(f"Warning: Could not load CollaborationHub: {e}", file=sys.stderr) + return self._collaboration_hub + + @property + def shared_memory(self): + """Lazy load the Shared Memory.""" + if self._shared_memory is None: + try: + from sessions.memory import SharedMemory + self._shared_memory = SharedMemory() + except ImportError as e: + print(f"Warning: Could not load SharedMemory: {e}", file=sys.stderr) + return self._shared_memory + # ========================================================================= # Tool Implementations # ========================================================================= @@ -536,6 +780,215 @@ async def tool_get_node_config(self, node: str) -> Dict[str, Any]: return {"node": node, "config": config} + # ========================================================================= + # Session Management Tool Implementations + # ========================================================================= + + async def tool_session_register( + self, + session_id: str, + agent_name: str, + agent_type: str, + human_user: Optional[str] = None, + capabilities: Optional[List[str]] = None, + ) -> Dict[str, Any]: + """Register a new session.""" + if not self.session_registry: + return {"error": "Session registry not available"} + + session = self.session_registry.register( + session_id=session_id, + agent_name=agent_name, + agent_type=agent_type, + human_user=human_user, + capabilities=capabilities or [], + ) + + return { + "success": True, + "session": session.to_dict(), + "message": f"Registered session {session_id}", + } + + async def tool_session_list(self, include_offline: bool = False) -> Dict[str, Any]: + """List active sessions.""" + if not self.session_registry: + return {"error": "Session registry not available"} + + sessions = self.session_registry.list_sessions(include_offline=include_offline) + + return { + "sessions": [s.to_dict() for s in sessions], + "count": len(sessions), + } + + async def tool_session_ping(self, from_session: str, to_session: str) -> Dict[str, Any]: + """Ping a session.""" + if not self.collaboration_hub: + return {"error": "Collaboration hub not available"} + + # Update registry ping + if self.session_registry: + self.session_registry.ping(from_session) + + # Send collaboration ping + message = self.collaboration_hub.ping_session(from_session, to_session) + + return { + "success": True, + "message": message.to_dict(), + "signal": message.format_signal(), + } + + async def tool_collab_send( + self, + from_session: str, + to_session: str, + subject: str, + body: str, + message_type: str = "request", + data: Optional[Dict[str, Any]] = None, + ) -> Dict[str, Any]: + """Send a collaboration message.""" + if not self.collaboration_hub: + return {"error": "Collaboration hub not available"} + + from sessions.collaboration import MessageType + + msg_type = MessageType(message_type) + + message = self.collaboration_hub.send( + from_session=from_session, + to_session=to_session, + type=msg_type, + subject=subject, + body=body, + data=data, + ) + + return { + "success": True, + "message": message.to_dict(), + "signal": message.format_signal(), + } + + async def tool_collab_broadcast( + self, + from_session: str, + subject: str, + body: str, + data: Optional[Dict[str, Any]] = None, + ) -> Dict[str, Any]: + """Broadcast a message to all sessions.""" + if not self.collaboration_hub: + return {"error": "Collaboration hub not available"} + + message = self.collaboration_hub.broadcast( + from_session=from_session, + subject=subject, + body=body, + data=data, + ) + + return { + "success": True, + "message": message.to_dict(), + "signal": message.format_signal(), + } + + async def tool_collab_get_messages( + self, + session_id: str, + message_type: Optional[str] = None, + ) -> Dict[str, Any]: + """Get messages for a session.""" + if not self.collaboration_hub: + return {"error": "Collaboration hub not available"} + + from sessions.collaboration import MessageType + + msg_type = MessageType(message_type) if message_type else None + + messages = self.collaboration_hub.get_messages( + session_id=session_id, + message_type=msg_type, + ) + + return { + "messages": [msg.to_dict() for msg in messages], + "count": len(messages), + } + + async def tool_memory_set( + self, + session_id: str, + key: str, + value: Any, + memory_type: str = "state", + tags: Optional[List[str]] = None, + ) -> Dict[str, Any]: + """Store a value in shared memory.""" + if not self.shared_memory: + return {"error": "Shared memory not available"} + + from sessions.memory import MemoryType + + mem_type = MemoryType(memory_type) + + entry = self.shared_memory.set( + session_id=session_id, + key=key, + value=value, + type=mem_type, + tags=tags or [], + ) + + return { + "success": True, + "entry": entry.to_dict(), + "message": f"Stored {key} in shared memory", + } + + async def tool_memory_get(self, key: str) -> Dict[str, Any]: + """Get a value from shared memory.""" + if not self.shared_memory: + return {"error": "Shared memory not available"} + + value = self.shared_memory.get(key) + + if value is None: + return {"found": False, "key": key} + + return { + "found": True, + "key": key, + "value": value, + } + + async def tool_memory_search( + self, + pattern: Optional[str] = None, + tags: Optional[List[str]] = None, + session_id: Optional[str] = None, + ) -> Dict[str, Any]: + """Search shared memory.""" + if not self.shared_memory: + return {"error": "Shared memory not available"} + + if pattern: + entries = self.shared_memory.search(pattern) + elif tags: + entries = self.shared_memory.get_by_tags(tags) + elif session_id: + entries = self.shared_memory.get_by_session(session_id) + else: + return {"error": "Specify pattern, tags, or session_id"} + + return { + "entries": [e.to_dict() for e in entries], + "count": len(entries), + } + # ========================================================================= # Resource Implementations # ========================================================================= @@ -648,6 +1101,16 @@ async def _handle_tools_call(self, params: Dict) -> Dict[str, Any]: "process_webhook": self.tool_process_webhook, "get_signals": self.tool_get_signals, "get_node_config": self.tool_get_node_config, + # Session management tools + "session_register": self.tool_session_register, + "session_list": self.tool_session_list, + "session_ping": self.tool_session_ping, + "collab_send": self.tool_collab_send, + "collab_broadcast": self.tool_collab_broadcast, + "collab_get_messages": self.tool_collab_get_messages, + "memory_set": self.tool_memory_set, + "memory_get": self.tool_memory_get, + "memory_search": self.tool_memory_search, } if tool_name not in tool_map: diff --git a/prototypes/sessions/demo.py b/prototypes/sessions/demo.py new file mode 100644 index 0000000..277c1bf --- /dev/null +++ b/prototypes/sessions/demo.py @@ -0,0 +1,347 @@ +#!/usr/bin/env python3 +""" +Demo script for BlackRoad Session Management. + +Shows how sessions can discover each other, collaborate, and share memory. +""" + +import sys +import time +from pathlib import Path + +# Add sessions to path +sys.path.insert(0, str(Path(__file__).parent)) + +from sessions import ( + SessionRegistry, SessionStatus, + CollaborationHub, MessageType, + SharedMemory, MemoryType +) + + +def print_section(title): + """Print a section header.""" + print("\n" + "=" * 60) + print(f" {title}") + print("=" * 60) + + +def demo_session_discovery(): + """Demo: Register and discover sessions.""" + print_section("DEMO 1: Session Discovery") + + registry = SessionRegistry() + + # Register Cece (Claude) + print("\nπŸ“ Registering Cece (Claude)...") + cece = registry.register( + session_id="cece-001", + agent_name="Cece", + agent_type="Claude", + human_user="Alexa", + capabilities=["python", "planning", "review"] + ) + print(f" βœ… {cece.agent_name} registered: {cece.session_id}") + + # Register another agent (GPT-4) + print("\nπŸ“ Registering Agent-2 (GPT-4)...") + agent2 = registry.register( + session_id="agent-002", + agent_name="Agent-2", + agent_type="GPT-4", + human_user="Alexa", + capabilities=["javascript", "react", "testing"] + ) + print(f" βœ… {agent2.agent_name} registered: {agent2.session_id}") + + # List all sessions + print("\nπŸ“‹ Listing all active sessions:") + sessions = registry.list_sessions() + for session in sessions: + print(f" β€’ {session.agent_name} ({session.agent_type}) - {session.status.value}") + if session.capabilities: + print(f" Capabilities: {', '.join(session.capabilities)}") + + # Find Python expert + print("\nπŸ” Finding Python experts...") + python_experts = registry.find_sessions(capability="python") + for session in python_experts: + print(f" β€’ {session.agent_name} can help with Python!") + + # Update status + print("\nβš™οΈ Cece starts working...") + registry.update_status("cece-001", SessionStatus.WORKING, "Building collaboration system") + + # Show stats + print("\nπŸ“Š Session Statistics:") + stats = registry.get_stats() + print(f" Total: {stats['total_sessions']}") + print(f" Active: {stats['active_sessions']}") + print(f" By status: {stats['by_status']}") + + +def demo_collaboration(): + """Demo: Inter-session collaboration.""" + print_section("DEMO 2: Collaboration Messages") + + hub = CollaborationHub() + + # Ping another session + print("\nπŸ”” Cece pings Agent-2...") + ping = hub.ping_session("cece-001", "agent-002") + print(f" {ping.format_signal()}") + + # Request help + print("\n❓ Cece requests help with React...") + request = hub.send( + from_session="cece-001", + to_session="agent-002", + type=MessageType.REQUEST, + subject="React component review", + body="Can you review this React component for me?", + data={"component": "UserProfile.jsx", "lines": 150} + ) + print(f" {request.format_signal()}") + + # Agent-2 responds + print("\nβœ… Agent-2 responds...") + response = hub.reply( + from_session="agent-002", + to_message=request, + body="Sure! I'll review it now. Looks good overall, minor suggestions in comments.", + data={"approved": True, "suggestions": 3} + ) + print(f" {response.format_signal()}") + + # Broadcast announcement + print("\nπŸ“‘ Cece broadcasts to all sessions...") + broadcast = hub.broadcast( + from_session="cece-001", + subject="Deployment scheduled", + body="Production deployment scheduled for 2PM" + ) + print(f" {broadcast.format_signal()}") + + # Get messages for Agent-2 + print("\nπŸ“¬ Agent-2 checks messages...") + messages = hub.get_messages("agent-002") + print(f" Received {len(messages)} messages:") + for msg in messages: + print(f" β€’ [{msg.type.value}] {msg.subject}") + + # Show conversation thread + print("\nπŸ’¬ Full conversation thread:") + thread = hub.get_conversation(request.message_id) + for msg in thread: + print(f" {msg.timestamp}") + print(f" {msg.from_session} β†’ {msg.to_session}") + print(f" {msg.body}") + print() + + +def demo_shared_memory(): + """Demo: Shared memory across sessions.""" + print_section("DEMO 3: Shared Memory") + + memory = SharedMemory() + + # Cece stores project plan + print("\n🧠 Cece stores project plan in shared memory...") + memory.set( + session_id="cece-001", + key="project_plan", + value={ + "phase": "design", + "tasks": ["api-design", "database-schema", "frontend-mockups"], + "deadline": "2026-02-01" + }, + type=MemoryType.STATE, + tags=["project", "active", "design"] + ) + print(" βœ… Stored project_plan") + + # Agent-2 reads the plan + print("\nπŸ“– Agent-2 reads project plan from shared memory...") + plan = memory.get("project_plan") + print(f" Phase: {plan['phase']}") + print(f" Tasks: {', '.join(plan['tasks'])}") + print(f" Deadline: {plan['deadline']}") + + # Agent-2 stores task progress + print("\nπŸ“ Agent-2 stores task progress...") + memory.set( + session_id="agent-002", + key="task_api-design", + value={ + "status": "completed", + "owner": "agent-002", + "completed_at": "2026-01-27" + }, + type=MemoryType.TASK, + tags=["task", "completed"] + ) + print(" βœ… Stored task_api-design") + + # Cece searches for tasks + print("\nπŸ” Cece searches for all tasks...") + tasks = memory.search("task_*") + print(f" Found {len(tasks)} task(s):") + for entry in tasks: + print(f" β€’ {entry.key}: {entry.value['status']}") + + # Find by tags + print("\n🏷️ Finding all active items by tag...") + active_items = memory.get_by_tags(["active"]) + print(f" Found {len(active_items)} active item(s):") + for entry in active_items: + print(f" β€’ {entry.key} (by {entry.session_id})") + + # Show stats + print("\nπŸ“Š Shared Memory Statistics:") + stats = memory.get_stats() + print(f" Total entries: {stats['total_entries']}") + print(f" Unique keys: {stats['unique_keys']}") + print(f" Unique sessions: {stats['unique_sessions']}") + + +def demo_full_workflow(): + """Demo: Complete collaborative workflow.""" + print_section("DEMO 4: Complete Workflow") + + registry = SessionRegistry() + hub = CollaborationHub(registry) + memory = SharedMemory() + + print("\n🎯 Scenario: Multi-agent code review workflow") + print(" Agents: Planner, Developer, Reviewer") + + # Register agents + print("\n1️⃣ Registering agents...") + registry.register("planner-001", "Planner", "Claude", "Alexa", ["planning", "architecture"]) + registry.register("developer-001", "Developer", "GPT-4", "Alexa", ["python", "coding"]) + registry.register("reviewer-001", "Reviewer", "Claude", "Alexa", ["review", "security"]) + print(" βœ… All agents registered") + + # Planner creates plan + print("\n2️⃣ Planner creates implementation plan...") + memory.set( + "planner-001", + "implementation_plan", + {"feature": "user-auth", "steps": ["design", "implement", "test", "review"]}, + MemoryType.STATE, + tags=["plan", "auth"] + ) + hub.broadcast("planner-001", "Plan ready", "Implementation plan for user-auth is ready") + print(" βœ… Plan created and broadcast") + + # Developer reads and implements + print("\n3️⃣ Developer reads plan and implements...") + plan = memory.get("implementation_plan") + registry.update_status("developer-001", SessionStatus.WORKING, f"Implementing {plan['feature']}") + print(f" βš™οΈ Developer working on {plan['feature']}") + + # Developer stores code + memory.set( + "developer-001", + "code_user-auth", + {"file": "auth.py", "lines": 250, "tests": "passing"}, + MemoryType.STATE, + tags=["code", "ready-for-review"] + ) + + # Developer requests review + hub.send( + "developer-001", + "reviewer-001", + MessageType.TASK_OFFER, + "Code review needed", + "User auth implementation ready for review", + data={"file": "auth.py", "priority": "high"} + ) + print(" βœ… Code complete, review requested") + + # Reviewer accepts and reviews + print("\n4️⃣ Reviewer accepts and reviews code...") + hub.send( + "reviewer-001", + "developer-001", + MessageType.TASK_ACCEPT, + "Starting review", + "Will review the auth code now" + ) + registry.update_status("reviewer-001", SessionStatus.WORKING, "Reviewing auth.py") + + # Reviewer provides feedback + memory.set( + "reviewer-001", + "review_user-auth", + {"status": "approved", "issues": 0, "suggestions": 2}, + MemoryType.DECISION, + tags=["review", "approved"] + ) + + hub.send( + "reviewer-001", + "developer-001", + MessageType.RESPONSE, + "Review complete", + "Code looks great! LGTM with 2 minor suggestions.", + data={"approved": True} + ) + print(" βœ… Review complete - approved!") + + # Show final state + print("\n5️⃣ Final state:") + sessions = registry.list_sessions() + for session in sessions: + print(f" β€’ {session.agent_name}: {session.status.value}") + if session.current_task: + print(f" Task: {session.current_task}") + + # Show collaboration stats + print("\nπŸ“Š Workflow Statistics:") + collab_stats = hub.get_stats() + memory_stats = memory.get_stats() + print(f" Messages exchanged: {collab_stats['total_messages']}") + print(f" Memory entries created: {memory_stats['total_entries']}") + + +def main(): + """Run all demos.""" + print("\n" + "=" * 60) + print(" BlackRoad Session Management - Live Demo") + print(" [COLLABORATION] + [MEMORY] for the Mesh") + print("=" * 60) + + try: + # Run demos + demo_session_discovery() + input("\nPress Enter to continue to Collaboration demo...") + + demo_collaboration() + input("\nPress Enter to continue to Shared Memory demo...") + + demo_shared_memory() + input("\nPress Enter to continue to Full Workflow demo...") + + demo_full_workflow() + + print("\n" + "=" * 60) + print(" βœ… Demo Complete!") + print(" Session management is now active in the Bridge.") + print("=" * 60) + print("\nTry it yourself:") + print(" python -m sessions register ") + print(" python -m sessions list") + print(" python -m sessions send ") + print(" python -m sessions memory-set ") + print("\nSee README.md for full documentation.") + + except Exception as e: + print(f"\n❌ Error: {e}") + import traceback + traceback.print_exc() + + +if __name__ == "__main__": + main() diff --git a/prototypes/sessions/sessions/__init__.py b/prototypes/sessions/sessions/__init__.py index d00de69..728f773 100644 --- a/prototypes/sessions/sessions/__init__.py +++ b/prototypes/sessions/sessions/__init__.py @@ -6,7 +6,7 @@ from .registry import SessionRegistry, Session, SessionStatus from .collaboration import CollaborationHub, Message, MessageType -from .memory import SharedMemory, MemoryEntry +from .memory import SharedMemory, MemoryEntry, MemoryType __all__ = [ "SessionRegistry", @@ -17,6 +17,7 @@ "MessageType", "SharedMemory", "MemoryEntry", + "MemoryType", ] __version__ = "0.1.0" diff --git a/prototypes/sessions/update_status.py b/prototypes/sessions/update_status.py new file mode 100644 index 0000000..a93af41 --- /dev/null +++ b/prototypes/sessions/update_status.py @@ -0,0 +1,110 @@ +""" +Script to update .STATUS beacon with active sessions. +""" + +import sys +from pathlib import Path +from datetime import datetime + +# Add sessions to path +sys.path.insert(0, str(Path(__file__).parent.parent / "prototypes" / "sessions")) + +from sessions.registry import SessionRegistry +from sessions.collaboration import CollaborationHub +from sessions.memory import SharedMemory + + +def update_status_beacon(): + """Update .STATUS with current session information.""" + + # Get bridge root + bridge_root = Path(__file__).parent.parent + status_file = bridge_root / ".STATUS" + + # Initialize session systems + registry = SessionRegistry() + hub = CollaborationHub(registry) + memory = SharedMemory() + + # Get stats + session_stats = registry.get_stats() + collab_stats = hub.get_stats() + memory_stats = memory.get_stats() + + # Get active sessions + sessions = registry.list_sessions() + + # Read current status + if status_file.exists(): + with open(status_file, 'r') as f: + lines = f.readlines() + else: + lines = [] + + # Find session section or create it + session_section_start = -1 + session_section_end = -1 + + for i, line in enumerate(lines): + if "# ACTIVE SESSIONS" in line: + session_section_start = i + elif session_section_start >= 0 and line.startswith("# ═══"): + session_section_end = i + break + + # Build session section + session_lines = [ + "# ═══════════════════════════════════════\n", + "# ACTIVE SESSIONS ([COLLABORATION] + [MEMORY])\n", + "# ═══════════════════════════════════════\n", + "\n", + f"total_sessions: {session_stats['total_sessions']}\n", + f"active_sessions: {session_stats['active_sessions']}\n", + f"collaboration_messages: {collab_stats['total_messages']}\n", + f"shared_memory_entries: {memory_stats['total_entries']}\n", + "\n", + ] + + if sessions: + session_lines.append("# Active session list:\n") + for session in sessions[:10]: # Limit to 10 most recent + status_emoji = { + "active": "🟒", + "working": "βš™οΈ", + "idle": "⏸️", + "waiting": "⏳", + "offline": "βšͺ", + }.get(session.status.value, "πŸ”΅") + + session_lines.append( + f"# {status_emoji} {session.session_id}: {session.agent_name} ({session.agent_type})" + ) + if session.current_task: + session_lines.append(f" - {session.current_task}") + session_lines.append("\n") + else: + session_lines.append("# No active sessions\n") + + session_lines.append("\n") + + # Update or append session section + if session_section_start >= 0: + # Replace existing section + if session_section_end < 0: + session_section_end = len(lines) + lines = lines[:session_section_start] + session_lines + lines[session_section_end + 1:] + else: + # Append new section + if lines and not lines[-1].endswith('\n'): + lines.append('\n') + lines.extend(session_lines) + + # Write back + with open(status_file, 'w') as f: + f.writelines(lines) + + print(f"βœ… Updated .STATUS beacon with {session_stats['active_sessions']} active sessions") + + +if __name__ == "__main__": + update_status_beacon()