How Slash Commands Are Routed in the Hermes TUI Gateway

The article explains the design of Hermes' terminal UI gateway, detailing why the TUI does not import the Agent directly, how JSON‑RPC over stdio or WebSocket enables local and remote modes, and how slash commands are processed through a two‑layer routing system with a dedicated _SlashWorker subprocess and a pure‑function crash‑recovery plan.

James' Growth Diary
James' Growth Diary
James' Growth Diary
How Slash Commands Are Routed in the Hermes TUI Gateway

01 | Background: Why TUI Doesn't Import Agent Directly

A naive implementation would import the Agent code into the TUI process, but Hermes needs to support both local mode (TUI on the user’s machine) and remote mode (Gateway on a server). Direct import would break remote mode. Mixing I/O‑heavy UI work with compute‑heavy LLM calls also risks a single‑process crash. Protocolizing the interaction lets TUI and Gateway be tested independently, with planGatewayRecovery as a pure, zero‑dependency function.

02 | Industry Research: UI‑Core Separation in Terminal AI Tools

Hermes follows patterns used by other tools:

LSP (Language Server Protocol) : VS Code separates editor UI from language services via JSON‑RPC over stdio.

DAP (Debug Adapter Protocol) : Debugger UI is separated; events are pushed with an event message, similar to Hermes' gateway event push.

Claude Code : Packs CLI and UI in one Node process, which is simple but cannot be remote or independently tested.

Hermes Choice : Implements a dual‑mode JSON‑RPC protocol that works over stdin/stdout for local development and WebSocket for remote dashboards.

03 | Design Conclusion: JSON‑RPC over stdio + Dual‑Mode Connection

Core principles:

Uniform protocol format – request, response, and event messages follow JSON‑RPC 2.0.

Automatic connection mode – if HERMES_TUI_GATEWAY_URL is set, TUI connects via WebSocket; otherwise it spawns a local Gateway subprocess.

Two‑layer slash command routing – UI‑layer commands (/help, /quit, /model, /mouse) are handled inside the TUI process; Agent‑layer commands (/compact, plugins, etc.) are sent to the Gateway via slash.exec.

04 | Dual‑Mode Connection: Local Spawn vs Remote Attach

The TUI side uses GatewayClient (src/gatewayClient.ts) to manage communication:

export class GatewayClient {
  private _ws: WebSocket | null = null
  private _proc: ChildProcess | null = null

  async connect() {
    const remoteUrl = process.env.HERMES_TUI_GATEWAY_URL
    if (remoteUrl) {
      // Remote mode: attach via WebSocket
      this._ws = new WebSocket(remoteUrl)
      await this._waitForOpen()
    } else {
      // Local mode: spawn Python subprocess and use stdio transport
      this._proc = spawn('python', ['-m', 'tui_gateway.entry'], {
        stdio: ['pipe', 'pipe', 'pipe'],
      })
      this._setupStdioTransport(this._proc)
    }
  }

  // Unified RPC method, works for both WS and stdio
  rpc<T>(method: string, params: object): Promise<T> {
    return new Promise((resolve, reject) => {
      const id = ++this._seq
      this._pending.set(id, { resolve, reject })
      this._send({ jsonrpc: '2.0', id, method, params })
    })
  }
}

The Gateway side reads JSON‑RPC lines from stdin, dispatches them, and writes responses to stdout.

05 | Two‑Layer Slash Command Routing

When the TUI receives input, it first checks if the text starts with a slash:

export function looksLikeSlashCommand(text: string): boolean {
  return text.trimStart().startsWith('/')
}

export function parseSlashCommand(text: string): { name: string; arg: string } {
  const trimmed = text.trim().slice(1)
  const [name, ...rest] = trimmed.split(/\s+/)
  return { name: name.toLowerCase(), arg: rest.join(' ') }
}

Parsed commands are looked up in SLASH_COMMANDS (registry.ts). Local commands are listed in LOCAL_COMMANDS and handled directly; all other commands are forwarded to the Gateway via slash.exec.

Gateway enforces a whitelist:

Skill commands – rejected with error 4018, must use command.dispatch.

Pending‑input commands (/y, /n, etc.) – also rejected, intended for Agent confirmation.

Plugin commands – allowed, routed to the plugin handler.

Regular commands (/compact, /clear, …) – sent to the _SlashWorker subprocess.

06 | _SlashWorker: Dedicated Subprocess Design

_SlashWorker

runs as a separate Python process per session, isolating potentially slow slash commands (e.g., /compact that triggers an LLM call) from the main Gateway loop.

Communication protocol (JSON‑RPC line with id and command) and timeout handling are implemented as follows:

def run(self, command: str) -> str:
    if self.proc.poll() is not None:
        raise RuntimeError("slash worker exited")
    with self._lock:
        self._seq += 1
        rid = self._seq
        self.proc.stdin.write(json.dumps({"id": rid, "command": command}) + "
")
        self.proc.stdin.flush()
        while True:
            try:
                msg = self.stdout_queue.get(timeout=_SLASH_WORKER_TIMEOUT_S)
            except queue.Empty:
                raise RuntimeError("slash worker timed out")
            if msg is None:
                break
            if msg.get("id") != rid:
                continue
            if not msg.get("ok"):
                raise RuntimeError(msg.get("error", "slash worker failed"))
            return str(msg.get("output", "")).rstrip()

Timeout defaults to 45 seconds (minimum 5 seconds) and is configurable via HERMES_TUI_SLASH_TIMEOUT_S. If a timeout occurs, the Gateway does not kill the worker; it simply stops waiting for that request.

07 | Crash Self‑Healing: planGatewayRecovery

planGatewayRecovery

is a pure function that decides whether to restart a crashed Gateway and which session to resume. It limits automatic restarts to three attempts within a 60‑second sliding window.

export const GATEWAY_RECOVERY_LIMIT = 3
export const GATEWAY_RECOVERY_WINDOW_MS = 60_000

export function planGatewayRecovery(liveSid: null | string, recoverSid: null | string, attempts: number[], now: number): RecoveryPlan {
  const sid = liveSid ?? recoverSid
  const recent = attempts.filter(t => now - t < GATEWAY_RECOVERY_WINDOW_MS)
  const recover = Boolean(sid) && recent.length < GATEWAY_RECOVERY_LIMIT
  return {
    attempts: recover ? [...recent, now] : recent,
    recover,
    sid,
  }
}

Test cases illustrate the budget logic and crash‑loop handling.

Common Pitfalls

Pitfall 1 : Sending local slash commands (e.g., /model, /sessions) through slash.exec yields error 4018. Use the LOCAL_COMMANDS list.

Pitfall 2 : slash.exec runs in _SlashWorker and is meant for output‑type commands; command.dispatch handles state‑changing commands.

Pitfall 3 : A worker timeout does not kill the process; the worker remains alive and may be reused for subsequent commands.

Pitfall 4 : In remote mode, HERMES_TUI_GATEWAY_URL must be a plain WebSocket endpoint (e.g., ws://host:8765) without a path suffix.

Summary

Hermes TUI Gateway’s core design is "protocol first, connection mode later": a single JSON‑RPC definition works over stdio for local development and WebSocket for remote dashboards, slash commands are split between fast UI‑side handling and full Gateway processing via a dedicated subprocess, and crash recovery is expressed as a testable pure function with a 60 s/3‑attempt budget. This architecture enables one‑click local startup and reliable long‑running server deployment.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

gatewayHermesCrash recoveryProcess isolationJSON-RPCTUISlash command
James' Growth Diary
Written by

James' Growth Diary

I am James, focusing on AI Agent learning and growth. I continuously update two series: “AI Agent Mastery Path,” which systematically outlines core theories and practices of agents, and “Claude Code Design Philosophy,” which deeply analyzes the design thinking behind top AI tools. Helping you build a solid foundation in the AI era.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.