Files
hermes-skills/hermes-vscode-remote/SKILL.md
Hermes Agent b0d790be34 Add all 104 active skills from all 16 Hermes profiles
12 unversioned skills now versioned at 1.0.0:
agent-communication, ascii-video, external-reasoning-augmentation,
jotty-notes-api, minecraft-modpack-server, obsidian, pokemon-player,
powerpoint, social-search, songwriting-and-ai-music,
workspace-context-organization, youtube-content

Total repo: 141 skills across all profile scopes
2026-07-04 11:44:04 -05:00

8.9 KiB
Raw Blame History

name, description, version, author, tags
name description version author tags
hermes-vscode-remote Connect Hermes Agent to VS Code when they run on different machines (LXC/remote server + local laptop). Covers ACP stdio-to-WebSocket bridging, API server vs ACP distinction, and profile selection. 1.0.0 agent
hermes
vscode
acp
remote
websocket
bridge

Hermes to VS Code — Remote Connection

When Hermes runs on a remote machine (LXC, VPS, Docker) and VS Code runs on your local laptop, ACP's stdio transport doesn't work directly. This skill covers the bridge setup.

Key Concepts

  • ACP (Agent Client Protocol) — stdio-based JSON-RPC protocol. VS Code launches the agent as a child process. This is the correct path for VS Code integration.
  • API Server — HTTP/OpenAI-compatible endpoint (port 8642). For Open WebUI, LobeChat, etc. NOT for VS Code.
  • Profile selection — per-process, not per-request. No ?profile=xxx parameter. Each profile needs its own gateway/ACP process.

Prerequisites (Hermes Side)

# Install ACP extra
cd ~/.hermes/hermes-agent
pip install -e '.[acp]'

# Verify
hermes acp --check   # should exit 0
hermes acp --version

Architecture

VS Code (laptop) → websocat (stdio↔ws) → LAN → stdio-to-ws (ws↔stdio) → hermes acp (LXC)

Step 1: Start the Bridge on Hermes Host

# Install the bridge
npm install -g @rebornix/stdio-to-ws --prefix ~/.local

# Start it (persistent, survives disconnects)
~/.local/bin/stdio-to-ws -p 8643 --persist --grace-period -1 "hermes -p <profile> acp"

Verify: ss -tlnp | grep 8643 should show listening.

Create a user service so the bridge survives reboots and crashes:

# Ensure linger is enabled (so user services survive logout)
sudo loginctl enable-linger $USER

Create ~/.config/systemd/user/hermes-acp-bridge.service:

[Unit]
Description=Hermes ACP stdio-to-ws bridge for VS Code
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
ExecStart=/home/<user>/.local/bin/stdio-to-ws -p 8643 --persist --grace-period -1 "/home/<user>/.local/bin/hermes -p <profile> acp"
Restart=always
RestartSec=5
Environment=HOME=/home/<user>
Environment=HERMES_HOME=/home/<user>/.hermes
Environment=PATH=/home/<user>/.local/bin:/usr/local/bin:/usr/bin:/bin

[Install]
WantedBy=default.target

Then enable and start:

systemctl --user daemon-reload
systemctl --user enable --now hermes-acp-bridge.service
systemctl --user status hermes-acp-bridge.service   # verify

A ready-to-copy template is at templates/hermes-acp-bridge.service.

Files

  • templates/hermes-acp-bridge.service — copy-paste systemd unit (fill in <user>)
  • scripts/acp_repro.py — JSON-RPC traffic probe (see Debugging section below)

Step 2: Configure VS Code on Laptop

  1. Install ACP Client extension (formulahendry.acp-client)
  2. Install websocat (WebSocket ↔ stdio bridge):
  3. Add to VS Code settings.json:
{
  "acp.agents": {
    "Hermes (<profile>)": {
      "command": "websocat",
      "args": ["ws://<hermes-host>:8643"]
    }
  }
}
  1. Open ACP Client panel, select the agent, connect.

Pitfalls

  • ACP is stdio-only. There is no --host/--port flag on hermes acp. The bridge is mandatory for remote use.
  • API server is the wrong tool for VS Code. Port 8642 serves OpenAI-format HTTP — VS Code ACP Client doesn't speak it.
  • Profile is locked at process start. hermes -p vscode acp serves the vscode profile. To switch profiles, stop and restart with a different -p.
  • API_SERVER_KEY is required when binding the API server to 0.0.0.0. ACP has no such requirement.
  • npm global install may need --prefix ~/.local if the user lacks root on the Hermes host.
  • pip install -e '.[acp]' may downgrade packages (openai, requests, etc.) to match Hermes's pinned versions. The dependency conflicts are cosmetic — ACP still works.
  • EADDRINUSE on systemd restart. If the bridge was previously started manually (background process) and you switch to systemd, the old process still holds port 8643. Kill it first: ss -tlnp | grep 8643 to find the PID, then kill <pid>. The systemd service will auto-restart and bind successfully.
  • spawn hermes ENOENT in systemd. systemd's default PATH does not include ~/.local/bin. Use the full path /home/<user>/.local/bin/hermes in ExecStart= and add Environment=PATH=/home/<user>/.local/bin:/usr/local/bin:/usr/bin:/bin. The symptom is the bridge accepting WebSocket connections but immediately dropping them — check journalctl --user -u hermes-acp-bridge.service for the error. The bridge log will show [Child process error: Error: spawn hermes ENOENT] right when a client connects.
  • systemctl --user needs linger. Without loginctl enable-linger $USER, user services stop on logout. Check with loginctl show-user $USER | grep Linger.
  • stdio-to-ws takes ONLY the first positional arg as the command string. It uses parseArgsStringToArgv() to split that single string. Passing ["hermes", "-p", "vscode", "acp"] as separate args to the bridge will silently drop everything after hermes. Always pass the command as one quoted string: "hermes -p vscode acp". This bites when scripting the bridge from Python (asyncio.create_subprocess_exec).
  • stdio-to-ws sends a {"type":"connected","clientId":"..."} handshake before any ACP frames. Custom WebSocket clients must read past this handshake before expecting JSON-RPC responses.
  • stdio-to-ws strips LSP Content-Length: headers from incoming messages (message.replace(/^Content-Length: \d+\r?\n\r?\n/, "")). This is a no-op for ACP (newline-delimited JSON, no LSP framing) but worth knowing if you ever wire it to an LSP server.
  • acp library MessageSender.drain() blocks the response send. The acp Python SDK (v0.9.0) sends all ACP frames — notifications AND the final JSON-RPC response — through a single MessageSender queue. The send loop does await self._writer.drain() after every write. When the WebSocket transport is slow, drain() blocks indefinitely, queuing every subsequent frame behind it. The final session/prompt response (with stopReason) gets stuck behind the streaming notifications, so VS Code receives all the text but never gets the signal to clear the "thinking" spinner. Fix: patch MessageSender._loop() in the installed acp package to bound drain() with asyncio.wait_for(timeout=0.5). The payload is already in the writer's internal buffer after write(); the timeout just prevents the send loop from waiting forever for the OS pipe to drain. The bytes are still delivered when the consumer reads. File: /home/<user>/.local/lib/python3.13/site-packages/acp/task/sender.py. This patch gets overwritten on pip install --upgrade agent-client-protocol — re-apply or upstream a fix.

Verification

# On Hermes host
ss -tlnp | grep 8643          # bridge listening
hermes acp --check            # ACP deps OK

# On laptop
curl -s http://<hermes-host>:8643/ -H "Upgrade: websocket" -H "Connection: Upgrade" -o /dev/null -w "%{http_code}"
# Should return 400 (WebSocket upgrade attempted, not a plain HTTP endpoint)

Debugging ACP Behavior ("spinner never ends", "stopReason missing")

When VS Code reports weird ACP behavior (stuck "thinking" spinner, missing final response, no stopReason), the bug could live in three layers:

  1. Agent codeacp_adapter/server.py::prompt() returning PromptResponse
  2. Transport — stdio-to-ws bridge, websocat, or the WebSocket itself
  3. VS Code client — the formulahendry.acp-client extension

Before suspecting the agent code, isolate the transport. Run the repro script that spawns hermes acp directly and traces every JSON-RPC frame:

python3 scripts/acp_repro.py
python3 scripts/acp_repro.py --prompt "say hi"

The script sends initialize → session/new → session/prompt and prints every notification and response. Look for the final [RESP id=3] line — if it shows "stopReason": "end_turn", the agent is correct and the bug is in the transport or VS Code extension. If it's missing entirely, look at the agent's stderr (printed by the script) for stack traces.

This probe was the decisive tool when investigating a reported "missing stopReason" bug — it proved the agent was sending the response correctly and the issue was elsewhere (most likely the WebSocket bridge or the client).

Files

  • scripts/acp_repro.py — ACP JSON-RPC traffic trace (initialize → session/new → session/prompt)
  • templates/hermes-acp-bridge.service — systemd user unit for auto-start
  • references/acp-hang-investigation.md — root cause and fix for the "ACP spinner never ends" bug: MessageSender.drain() in the acp SDK blocks indefinitely, queuing the final response behind stuck writes. Includes the asyncio.wait_for(timeout=0.5) patch.