Head Node Overview
The Head Node is the local orchestrator component of Codecast. It runs on your local machine (or a control server) and manages all user-facing interactions, SSH connections, session state, and communication with remote daemons.
Technology Stack
- Language: Python 3.10+
- SSH: asyncssh for async SSH connections and tunnels
- HTTP Client: aiohttp for JSON-RPC and SSE streaming
- Discord: discord.py v2 with slash commands
- Telegram: python-telegram-bot v20+ with async handlers
- Lark/Feishu: lark-oapi SDK
- Database: SQLite via Python's built-in
sqlite3module - Config: YAML via
ruamel.yaml(comment-preserving round-trip editing)
Module Map
src/head/
├── cli.py # CLI entry point: argparse, subcommand dispatch
├── main.py # Head node entry: loads config, starts adapters, shutdown
├── config.py # Config dataclasses, YAML loader, env var expansion
├── engine.py # BotEngine: all command logic and message forwarding
├── ssh_manager.py # SSH connections, tunnels, daemon deployment
├── session_router.py # SQLite-backed session registry
├── daemon_client.py # JSON-RPC + SSE client for daemon communication
├── message_formatter.py # Output formatting, message splitting, tool batching
├── file_forward.py # File forwarding: detect and forward file paths to users
├── platform/
│ ├── protocol.py # PlatformAdapter protocol, MessageHandle, FileAttachment
│ ├── discord_adapter.py # Discord PlatformAdapter (discord.py v2)
│ ├── telegram_adapter.py # Telegram PlatformAdapter (python-telegram-bot v20+)
│ ├── lark_adapter.py # Lark/Feishu PlatformAdapter
│ ├── format_utils.py # markdown_to_telegram_html() and other format helpers
│ └── __init__.py
├── tui/ # Interactive TUI (Textual)
└── webui/ # Web UI (aiohttp)
Module Dependencies
main.py
├── config.py (load_config, Config)
├── ssh_manager.py (SSHManager)
├── session_router.py (SessionRouter)
├── daemon_client.py (DaemonClient)
├── platform/discord_adapter.py (DiscordAdapter)
├── platform/telegram_adapter.py (TelegramAdapter)
└── platform/lark_adapter.py (LarkAdapter)
engine.py (BotEngine)
├── platform/protocol.py (PlatformAdapter, MessageHandle, FileAttachment)
├── ssh_manager.py (SSHManager)
├── session_router.py (SessionRouter)
├── daemon_client.py (DaemonClient)
├── message_formatter.py (formatting functions)
└── file_forward.py (FileForwardMatcher)
platform/discord_adapter.py (DiscordAdapter)
├── platform/protocol.py (PlatformAdapter, MessageHandle, FileAttachment)
└── message_formatter.py (split_message, format_error, display_mode)
platform/telegram_adapter.py (TelegramAdapter)
├── platform/protocol.py (PlatformAdapter, MessageHandle, FileAttachment)
├── message_formatter.py (split_message)
└── platform/format_utils.py (markdown_to_telegram_html)
ssh_manager.py
└── config.py (Config, PeerConfig)
session_router.py
└── (standalone, uses sqlite3)
daemon_client.py
└── (standalone, uses aiohttp)
message_formatter.py
└── (standalone, no dependencies)
file_forward.py
└── (standalone)
Architecture: BotEngine + PlatformAdapter
The Head Node uses a composition pattern rather than inheritance. BotEngine (in engine.py) contains all command and streaming logic and holds a PlatformAdapter instance for platform-specific I/O.
Each platform adapter (Discord, Telegram, Lark) implements the PlatformAdapter protocol defined in platform/protocol.py. The protocol includes methods for:
- Sending and editing messages (
send_message,edit_message,delete_message) - File operations (
download_file,send_file) - Interaction state (
start_typing,stop_typing) - Interactive questions (
send_question) for AskUserQuestion handling - Lifecycle (
start,stop)
When a user message arrives, the platform adapter calls the registered InputHandler callback on the BotEngine, passing the channel ID, text, optional user ID, and any file attachments.
Lifecycle
- Startup (
main.py): Load config, create shared infrastructure instances (SSHManager, SessionRouter, DaemonClient), create one BotEngine per platform adapter, start adapters. - Command handling: User sends
/start gpu-1 /pathvia Discord/Telegram/Lark. The adapter callsengine.handle_input(), which routes tocmd_start(). This calls SSHManager to set up the tunnel and DaemonClient to create a session. - Message forwarding: User sends a regular message. BotEngine resolves the active session via SessionRouter, forwards to DaemonClient, streams the SSE response back to chat in real time.
- Shutdown: SIGTERM/SIGINT triggers graceful cleanup — stop adapters, close HTTP sessions, close SSH tunnels.
Shared Infrastructure
The core infrastructure components are created once in main.py and shared across all platform adapters:
- SSHManager: One instance manages all SSH connections and tunnels. Thread-safe through asyncio's single-threaded event loop.
- SessionRouter: One SQLite database (
sessions.db) tracks sessions across all platforms (Discord, Telegram, Lark). - DaemonClient: One aiohttp session handles all RPC calls to remote daemons.
Each platform gets its own BotEngine instance, but all engines share the same SSHManager, SessionRouter, and DaemonClient.