Repository navigation
Implement v3.0: Gateway architecture, skills system, and enhanced session persistence - #7
Conversation
Co-authored-by: LEEI1337 <119540123+LEEI1337@users.noreply.github.com>
Co-authored-by: LEEI1337 <119540123+LEEI1337@users.noreply.github.com>
Co-authored-by: LEEI1337 <119540123+LEEI1337@users.noreply.github.com>
Co-authored-by: LEEI1337 <119540123+LEEI1337@users.noreply.github.com>
Co-authored-by: LEEI1337 <119540123+LEEI1337@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
This PR implements v3.0 of Phantom Neural Cortex, adding OpenClaw-inspired features while maintaining existing capabilities. The implementation adds a centralized gateway architecture on port 18789, a hot-reloadable skills system, and enhanced session persistence.
Changes:
- Gateway architecture with WebSocket server, session management, message routing, and health monitoring
- Skills system with hot-reload capability, registry, loader, sandbox execution, and example GitHub automation skill
- Integration of new features into main application with graceful degradation on failures
Reviewed changes
Copilot reviewed 21 out of 21 changed files in this pull request and generated 31 comments.
Show a summary per file
| File | Description |
|---|---|
gateway/__init__.py |
Package exports for gateway module |
gateway/config.py |
Configuration management with environment variable loading |
gateway/server.py |
WebSocket gateway server with Socket.IO integration |
gateway/session.py |
Session lifecycle management with Redis/memory persistence |
gateway/router.py |
Message routing with per-session queues |
gateway/health.py |
Health monitoring with component status tracking |
skills/__init__.py |
Package exports for skills module |
skills/base.py |
Base Skill class with metadata and lifecycle hooks |
skills/registry.py |
Skill discovery, loading, and management |
skills/loader.py |
Dynamic skill loader with auto-discovery |
skills/sandbox.py |
Sandboxed skill execution with timeout protection |
skills/community/__init__.py |
Community skills package marker |
skills/community/github_automation.py |
Example GitHub automation skill (mock implementation) |
dashboard/backend/main.py |
Integration of gateway and skills into main application |
dashboard/backend/demo_v3_features.py |
Demonstration script for all v3.0 features |
docs/V3_FEATURES.md |
Comprehensive feature documentation |
V3_IMPLEMENTATION_COMPLETE.md |
Implementation completion report |
V3_FILES_REFERENCE.md |
Quick reference for all new files |
README.md |
Updated with v3.0 announcement |
CHANGELOG.md |
Detailed v3.0 release notes |
.env.example |
Added 15+ new configuration variables |
| async def on_load(self): | ||
| """Called when skill is loaded""" | ||
| self.logger.info(f"Skill {self.metadata.name} loaded") | ||
|
|
||
| async def on_unload(self): |
There was a problem hiding this comment.
The on_enable and on_disable lifecycle methods are called but not defined in the base Skill class. These methods should be added to the base class in skills/base.py, similar to how on_load and on_unload are already implemented.
| async def on_load(self): | |
| """Called when skill is loaded""" | |
| self.logger.info(f"Skill {self.metadata.name} loaded") | |
| async def on_unload(self): | |
| async def on_load(self) -> None: | |
| """Called when skill is loaded""" | |
| self.logger.info(f"Skill {self.metadata.name} loaded") | |
| async def on_enable(self) -> None: | |
| """Called when skill is enabled""" | |
| self.status = SkillStatus.ENABLED | |
| self.logger.info(f"Skill {self.metadata.name} enabled") | |
| async def on_disable(self) -> None: | |
| """Called when skill is disabled""" | |
| self.status = SkillStatus.DISABLED | |
| self.logger.info(f"Skill {self.metadata.name} disabled") | |
| async def on_unload(self) -> None: |
| async def create_session( | ||
| self, | ||
| user_id: Optional[str] = None, | ||
| session_id: Optional[str] = None, | ||
| metadata: Optional[Dict[str, Any]] = None | ||
| ) -> Session: | ||
| """ | ||
| Create new session. | ||
|
|
||
| Args: | ||
| user_id: Optional user identifier | ||
| session_id: Optional session ID (generated if not provided) | ||
| metadata: Optional session metadata | ||
|
|
||
| Returns: | ||
| Created session | ||
| """ | ||
| if session_id is None: | ||
| session_id = str(uuid.uuid4()) | ||
|
|
||
| session = Session( | ||
| session_id=session_id, | ||
| user_id=user_id, | ||
| metadata=metadata or {} | ||
| ) | ||
|
|
||
| self._sessions[session_id] = session | ||
| await self._persist_session(session) | ||
|
|
||
| logger.info(f"Created session {session_id}") | ||
| return session |
There was a problem hiding this comment.
There is no validation to enforce the max_sessions limit defined in GatewayConfig. The create_session method will create new sessions indefinitely, which could lead to resource exhaustion. Add a check in create_session to reject new sessions when the limit is reached, or document that this is intentionally not enforced.
| if self.storage_backend == "redis" and self.redis_client: | ||
| key = f"session:{session.session_id}" | ||
| data = json.dumps(session.to_dict()) | ||
| await self.redis_client.set(key, data, ex=self.session_timeout) |
There was a problem hiding this comment.
The _persist_session method does not handle Redis errors. If the Redis connection fails or times out, the exception will propagate and could crash the application or leave sessions in an inconsistent state. Add try-except error handling similar to _load_sessions to gracefully handle Redis failures and log warnings while continuing operation.
| await self.redis_client.set(key, data, ex=self.session_timeout) | |
| try: | |
| await self.redis_client.set(key, data, ex=self.session_timeout) | |
| except Exception as e: | |
| logger.warning( | |
| "Failed to persist session %s to Redis: %s", | |
| session.session_id, | |
| e, | |
| ) |
| # Initialize Gateway (Phase 2) ✅ | ||
| try: | ||
| gateway_config = GatewayConfig.from_env() | ||
| gateway_server = GatewayServer(gateway_config) |
There was a problem hiding this comment.
The gateway initialization creates a GatewayServer instance but the Redis client needed for session persistence is not passed to it. The SessionManager is initialized inside GatewayServer.__init__ without a redis_client parameter, so even though the main application connects to Redis (line 45), the gateway's session manager won't use it. Consider passing the redis_manager.redis_client to the gateway configuration or session manager to enable actual Redis-based session persistence.
| gateway_server = GatewayServer(gateway_config) | |
| gateway_server = GatewayServer( | |
| gateway_config, | |
| redis_client=redis_manager.redis_client, | |
| ) |
| def from_env(cls) -> "GatewayConfig": | ||
| """Load configuration from environment variables""" | ||
| return cls( | ||
| host=os.getenv("GATEWAY_HOST", "0.0.0.0"), | ||
| port=int(os.getenv("GATEWAY_PORT", "18789")), | ||
| session_timeout=int(os.getenv("GATEWAY_SESSION_TIMEOUT", "3600")), | ||
| max_sessions=int(os.getenv("GATEWAY_MAX_SESSIONS", "1000")), | ||
| storage_backend=os.getenv("GATEWAY_STORAGE_BACKEND", "redis"), | ||
| redis_url=os.getenv("REDIS_URL"), | ||
| database_url=os.getenv("DATABASE_URL"), | ||
| max_message_size=int(os.getenv("GATEWAY_MAX_MESSAGE_SIZE", "10485760")), | ||
| message_queue_size=int(os.getenv("GATEWAY_MESSAGE_QUEUE_SIZE", "1000")), | ||
| health_check_interval=int(os.getenv("GATEWAY_HEALTH_CHECK_INTERVAL", "30")), | ||
| ) |
There was a problem hiding this comment.
The from_env method doesn't handle ValueError exceptions that can occur when converting environment variable strings to integers using int(). If a user provides a non-numeric value for any of these settings (PORT, SESSION_TIMEOUT, MAX_SESSIONS, etc.), the application will crash with an unhandled exception. Add try-except blocks or use pydantic's validation to provide better error messages.
|
|
||
| except Exception as e: | ||
| print(f"✗ Skills Demo Failed: {e}") | ||
| import traceback |
There was a problem hiding this comment.
This import of module traceback is redundant, as it was previously imported on line 218.
| try: | ||
| await self._check_task | ||
| except asyncio.CancelledError: | ||
| pass |
There was a problem hiding this comment.
'except' clause does nothing but pass and there is no explanatory comment.
| pass | |
| logger.debug( | |
| "HealthMonitor health check task cancelled during stop()" | |
| ) |
| process = psutil.Process() | ||
| memory_usage_mb = process.memory_info().rss / 1024 / 1024 | ||
| except ImportError: | ||
| pass |
There was a problem hiding this comment.
'except' clause does nothing but pass and there is no explanatory comment.
| pass | |
| # psutil is an optional dependency; fall back to 0.0 MB if unavailable | |
| logger.debug( | |
| "psutil not installed; memory usage metrics will be reported as 0.0 MB" | |
| ) | |
| memory_usage_mb = 0.0 |
| try: | ||
| await server.serve() | ||
| except KeyboardInterrupt: | ||
| pass |
There was a problem hiding this comment.
'except' clause does nothing but pass and there is no explanatory comment.
| pass | |
| logger.info("Received KeyboardInterrupt, shutting down gateway server") |
| try: | ||
| await self._cleanup_task | ||
| except asyncio.CancelledError: | ||
| pass |
There was a problem hiding this comment.
'except' clause does nothing but pass and there is no explanatory comment.
| pass | |
| # Expected during shutdown when the cleanup task is cancelled | |
| logger.debug("Cleanup task cancelled during SessionManager shutdown") |
Adds OpenClaw-inspired features while maintaining existing quality assessment and cost optimization capabilities. Implements centralized gateway (port 18789), hot-reloadable skills, and persistent sessions.
Gateway Architecture
Skills System
Skillclass with dynamic action discoveryContext Management Integration
/status,/context,/compactCLI commands available via REST APIConfiguration
15 new environment variables with backward-compatible defaults:
GATEWAY_PORT=18789 GATEWAY_STORAGE_BACKEND=redis # or memory SKILLS_DIR=skills/community SKILLS_HOT_RELOAD=trueNotes
Original prompt
💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.