<!-- Copyright (C) 2026 Stefy Lanza <stefy@nexlab.net> -->
<!-- SPDX-License-Identifier: GPL-3.0-or-later -->
<!-- Copyleft: GNU GPLv3 or later applies to this file. -->
# Hermes Node Protocol
# Copyright (c) 2026 Stefy (nextime) Lanza <stefy@nexlab.net>
# All rights reserved.
# 
# This software is released under the MIT License with a copyleft clause.
# See the LICENSE file for full terms.
# Hermes Node Protocol Specification

**Version:** 1.0  
**Date:** 2026-04-29  
**Purpose:** Reverse-connection node execution with an integrated node-local permission model enforced by the agent.

---

## Architecture Overview

```
┌─────────────────────────────────────────────────────────────┐
│                   Hermes Gateway                            │
│  ┌────────────────────────────────────────────────────┐    │
│  │ WebSocket Server (port 8765)                        │    │
│  │  - Accepts node connections                         │    │
│  │  - Authenticates via token                          │    │
│  │  - Maintains node registry                          │    │
│  │  - HTTP API for command submission                  │    │
│  └─────────────────────────────┬───────────────────────┘    │
│                                │ routes                     │
│  ┌─────────────────────────────▼───────────────────────┐    │
│  │ Command Router + Approval Engine                    │    │
│  │  - Matches commands to nodes                        │    │
│  │  - Handles "ask" list approval via user prompts     │    │
│  │  - Streams output back                               │    │
│  └────────────────────────────────────────────────────┘    │
└─────────────────────────────────────────────────────────────┘
         ▲                                 │
         │ WebSocket (node-initiated)       │ HTTP/WS
         │                                 │
┌────────┴─────────────────────────────────┴─────────────┐
│                  Remote Node Agent                       │
│  ┌──────────────────────────────────────────────────┐   │
│  │ WebSocket Client → connects to gateway           │   │
│  │  - Auto-reconnect on disconnect                   │   │
│  │  - Heartbeat every 30s                            │   │
│  │  - Handles auth token                             │   │
│  └───────────────────────────┬──────────────────────┘   │
│                              │ receives                  │
│  ┌───────────────────────────▼──────────────────────┐   │
│  │ Integrated Command Executor                       │   │
│  │  - Evaluates allow/deny/ask rules                 │   │
│  │  - Executes approved commands directly            │   │
│  │  - Streams stdout/stderr back                     │   │
│  │  - Returns exit code                              │   │
│  └──────────────────────────────────────────────────┘   │
└──────────────────────────────────────────────────────────┘
                                                              
  Permission System (config.json permissions block) - enforced by the agent
```

---

## Message Protocol

All messages are JSON over WebSocket.

### 1. Node → Gateway: Registration

**On connect:**
```json
{
  "type": "register",
  "node_name": "sissy",
  "version": "1.0",
  "capabilities": ["exec"],
  "capability_info": {
    "enable_browser": false,
    "enable_computer_control": false,
    "enable_desktop_observe": false,
    "enable_audio_control": false,
    "enable_camera_control": false
  }
}
```

**Gateway response:**
```json
{
  "type": "register_ack",
  "status": "ok",
  "node_id": "sissy",
  "gateway_version": "1.0"
}
```

### 2. Node → Gateway: Heartbeat

**Every 30 seconds:**
```json
{
  "type": "heartbeat",
  "timestamp": 1714392000
}
```

**Gateway response:**
```json
{
  "type": "heartbeat_ack",
  "timestamp": 1714392000
}
```

### 3. Gateway → Node: Execute Command

```json
{
  "type": "exec",
  "id": "cmd-a1b2c3d4",
  "command": ["df", "-h"],
  "timeout": 30,
  "approved": false
}
```

**Fields:**
- `id`: Unique command ID for tracking
- `command`: Array of command + args (e.g., `["df", "-h"]`)
- `timeout`: Max execution time in seconds
- `approved`: If `true`, bypass the node's `permissions.ask` rules (user explicitly approved)

### 4. Node → Gateway: Command Output (streaming)

**Stdout chunk:**
```json
{
  "type": "exec_output",
  "id": "cmd-a1b2c3d4",
  "stream": "stdout",
  "data": "Filesystem      Size  Used Avail Use% Mounted on\n"
}
```

**Stderr chunk:**
```json
{
  "type": "exec_output",
  "id": "cmd-a1b2c3d4",
  "stream": "stderr",
  "data": "warning: deprecated option\n"
}
```

### 5. Node → Gateway: Command Complete

```json
{
  "type": "exec_complete",
  "id": "cmd-a1b2c3d4",
  "exit_code": 0,
  "duration_ms": 1234
}
```

### 6. Node → Gateway: Approval Required

**When command matches the node's `permissions.ask` rules:**
```json
{
  "type": "exec_approval_required",
  "id": "cmd-a1b2c3d4",
  "command": ["sudo", "gnt-instance", "stop", "prod-db"],
  "reason": "Command matches ask pattern: 'sudo gnt-instance stop *'"
}
```

**Gateway forwards to user, then responds:**
```json
{
  "type": "exec_approval_response",
  "id": "cmd-a1b2c3d4",
  "approved": true,
  "add_to_allowlist": false
}
```

### 7. Node → Gateway: Command Denied

**When command matches the node's `permissions.deny` rules:**
```json
{
  "type": "exec_denied",
  "id": "cmd-a1b2c3d4",
  "command": ["rm", "-rf", "/"],
  "reason": "Command matches deny pattern: 'rm -rf /*'"
}
```

### 8. Gateway → Node: Disconnect

```json
{
  "type": "disconnect",
  "reason": "gateway_shutdown"
}
```

---

## Browser Control

**Optional capability** — requires Playwright on the node.

### Capability Registration

## Camera Control

**Optional capability** — phase-1 Linux support uses `ffmpeg` + V4L2 (`/dev/video*`). The node only advertises `camera_control` in its `tools` list when `enable_camera_control` is true *and* a usable camera backend/device is present at runtime.

### Actions

- `list_cameras` — enumerate detected camera devices and probe metadata
- `get_camera_status` — current camera backend readiness and discovered devices
- `capture_frame` — grab a single still frame from a selected/default device
- `capture_video` — record a short video clip from a selected/default device

### Gateway → Node: Camera Control

```json
{
  "type": "camera_control",
  "id": "camera-a1b2c3d4",
  "action": "capture_frame",
  "params": {
    "device": "/dev/video0",
    "format": "png",
    "width": 1280,
    "height": 720
  }
}
```

### Node → Gateway: Camera Control Result

```json
{
  "type": "camera_control_result",
  "id": "camera-a1b2c3d4",
  "action": "capture_frame",
  "success": true,
  "device": "/dev/video0",
  "format": "png",
  "path": "/tmp/hermes-camera-1714392000.png",
  "size_bytes": 123456,
  "data_base64": "..."
}
```

Node registers optional tools in its `tools` list during registration and exposes structured readiness metadata under `capabilities`. Existing siblings include `browser_control`, `computer_control`, `desktop_observe`, `audio_control`, and `camera_control`.

```json
{
  "type": "register",
  "node_name": "sissy",
  "version": "1.0",
  "capabilities": ["exec", "browser_control"],
  "capability_info": {
    "enable_browser": true,
    "enable_computer_control": false,
    "enable_desktop_observe": false,
    "enable_audio_control": false,
    "enable_camera_control": false
  }
}
```

---

## Authentication

### Token-Based Auth

**Node config file** (`/etc/hermes-node/config.json`):
```json
{
  "gateway_url": "wss://192.168.42.115:8765",
  "node_name": "sissy",
  "token": "node-sissy-secret-token-abc123",
  "reconnect_interval": 5,
  "heartbeat_interval": 30,
  "capabilities": ["exec"],
  "enable_browser": false,
  "enable_computer_control": false,
  "enable_desktop_observe": false,
  "enable_audio_control": false,
  "enable_camera_control": false,
  "permissions": {
    "deny": ["rm -rf /", "mkfs", "dd if="],
    "ask": ["sudo", "su", "passwd", "mount", "umount", "fdisk", "parted"],
    "allow": ["ls", "pwd", "whoami", "hostname", "uname", "date", "uptime", "df", "free", "ps", "cat", "head", "tail", "grep", "find", "du", "ip", "ifconfig", "ping", "curl", "wget", "git", "python", "python3", "node", "npm", "dmesg", "docker", "kubectl"]
  }
}
```

**WebSocket connection URL:**
```
ws://192.168.42.115:8765/nodes?token=node-sissy-secret-token-abc123
```

Gateway validates token against stored registry:
```json
{
  "sissy": "node-sissy-secret-token-abc123",
  "zeiss": "node-zeiss-secret-token-def456",
  "spank": "node-spank-secret-token-ghi789"
}
```

---

## Gateway HTTP API

For Hermes skill to submit commands.

### POST /nodes/{node_name}/exec

**Request:**
```json
{
  "command": ["df", "-h"],
  "timeout": 30,
  "approved": false
}
```

**Response (streaming):**
```json
{"type": "stdout", "data": "Filesystem      Size...\n"}
{"type": "stderr", "data": ""}
{"type": "exit", "code": 0}
```

### GET /nodes

**Response:**
```json
{
  "nodes": [
    {
      "name": "sissy",
      "status": "connected",
      "last_seen": 1714392000,
      "uptime": 86400
    },
    {
      "name": "zeiss",
      "status": "connected",
      "last_seen": 1714392005,
      "uptime": 172800
    }
  ]
}
```

### GET /nodes/{node_name}/status

**Response:**
```json
{
  "name": "sissy",
  "status": "connected",
  "last_seen": 1714392000,
  "uptime": 86400,
  "version": "1.0",
  "capabilities": ["exec"]
}
```

---

## Security Model

### 1. No Gateway SSH Keys
- Gateway never stores SSH keys for nodes
- Gateway never initiates connections to nodes
- Nodes connect out to gateway (firewall-friendly)

### 2. Token Authentication
- Each node has unique pre-shared token
- Tokens stored in `/etc/hermes-node/config.json` on node
- Tokens stored in gateway registry (file or DB)

### 3. Permission System
- Each node stores its permission rules in `config.json`
- `permissions.allow`: auto-execute
- `permissions.ask`: require user approval
- `permissions.deny`: reject immediately

### 4. Command Approval Flow
```
User → Hermes → Gateway → Node
                           ↓
                 agent checks config.json permissions
                           ↓
                 matches `permissions.ask`
                           ↓
                 sends approval_required
                           ↓
Gateway → User: "Approve 'sudo gnt-instance stop prod-db'?"
                           ↓
User: "yes"
                           ↓
Gateway → Node: approved=true
                           ↓
                 agent executes command
```

---

## Error Handling

### Node Disconnection
- Gateway marks node as "disconnected"
- Queued commands return error: `{"error": "node_offline"}`
- Node auto-reconnects with exponential backoff (5s, 10s, 20s, max 60s)

### Command Timeout
- If node doesn't respond within `timeout` seconds:
  - Gateway sends `{"type": "exec_cancel", "id": "..."}`
  - Node kills process
  - Returns `{"type": "exec_complete", "exit_code": -1, "error": "timeout"}`

### Gateway Restart
- Nodes detect disconnect via heartbeat failure
- Nodes reconnect automatically
- In-flight commands are lost (client must retry)

---

## Deployment

### Gateway Side
1. Install `hermes-node-gateway` service
2. Configure tokens in `/etc/hermes-node-gateway/tokens.json`
3. Start service: `/etc/init.d/hermes-node-gateway start`
4. Install `hermes-node-exec` skill for Hermes

### Node Side
1. Install `hermes-node-agent` package
2. Configure `/etc/hermes-node/config.json` with gateway URL + token
3. Review the `permissions` block and capability toggles for the node
4. Start service: `/etc/init.d/hermes-node-agent start`
5. Verify connection: `tail -f /var/log/hermes-node-agent.log`

---

## Compatibility

### With node-local permission policies
- ✅ Keeps the permission policy in `config.json`
- ✅ Preserves allow/deny/ask semantics
- ✅ Moves execution and approval checks fully into the agent
- ✅ Removes external wrapper dependencies from deployment

### Migration from earlier wrapper-based setups
1. Move any legacy allow/deny/ask rules into the node's `permissions` block
2. Install or upgrade the node agent
3. Configure gateway URL + token
4. Start agent
5. Remove obsolete wrapper references from local docs/scripts

---

## Future Enhancements

- **TLS/WSS support** for encrypted connections
- **Certificate-based auth** instead of tokens
- **Command history** and audit log
- **Multi-gateway failover** (node connects to backup if primary down)
- **Bidirectional file transfer** (upload/download via WebSocket)
- **Real-time log streaming** (tail -f over WebSocket)
