hermes-agent/docs/plans/2026-04-01-ink-gateway-tui-migration-plan.md
2026-04-02 19:07:53 -05:00

30 KiB

TUI Refactor: Current to Ideal

Date: 2026-04-01

Scope

  • same repo refactor
  • keep Python runtime
  • replace PT-based interactive shell
  • add Ink UI through a local gateway

Current Environment

Interactive path is centered in cli.py with prompt_toolkit and rich.

Current technical shape:

  • PT app shell and key handling in cli.py
    • Application, KeyBindings, TextArea, patch_stdout
  • queue control in cli.py
    • _pending_input
    • _interrupt_queue
  • approval and sudo callback globals in tools/terminal_tool.py
    • _approval_callback
    • _sudo_password_callback
  • runtime entry in run_agent.py
    • AIAgent.run_conversation()
    • AIAgent.chat()

Current constraint:

  • UI logic and runtime control are mixed, so UI replacement is expensive.

Ideal Environment

Interactive path is split into three layers:

  1. Ink UI (Node/TS)
  2. local tui_gateway over stdio JSON-RPC
  3. Python runtime (AIAgent, tools, sessions)

Rules for ideal state:

  • no direct UI to AIAgent calls
  • no PT dependency in gateway path
  • keep current Hermes state/config contracts
    • ~/.hermes/.env
    • ~/.hermes/config.yaml
    • ~/.hermes/state.db
    • profile behavior via HERMES_HOME

Migration Path

Cut 1: Headless Controller

Add:

  • tui_gateway/controller.py
  • tui_gateway/session_state.py
  • tui_gateway/events.py

Change:

  • run_agent.py callback wiring for controller events
  • cli.py compatibility bridge into controller

Done:

  • create/resume/prompt/interrupt/cancel work with no PT imports

Cut 2: Local Gateway

Add:

  • tui_gateway/protocol.py
  • tui_gateway/server.py
  • tui_gateway/entry.py

Methods:

  • session.create
  • session.resume
  • session.list
  • session.interrupt
  • session.cancel
  • prompt.submit
  • approval.respond
  • sudo.respond
  • clarify.respond

Events:

  • message.delta
  • tool.progress
  • approval.requested
  • sudo.requested
  • clarify.requested
  • error

Done:

  • simple client completes full prompt cycle through JSON-RPC

Cut 3: Ink UI

Add:

  • ui-tui/src/main.tsx
  • ui-tui/src/gatewayClient.ts
  • ui-tui/src/state/store.ts
  • ui-tui/src/components/Transcript.tsx
  • ui-tui/src/components/Composer.tsx
  • ui-tui/src/components/StatusBar.tsx
  • ui-tui/src/components/ApprovalModal.tsx
  • ui-tui/src/components/SudoPrompt.tsx
  • ui-tui/src/components/ClarifyPrompt.tsx

Change:

  • tools/terminal_tool.py prompt adapters for gateway round-trip

Done:

  • chat, tools, approval, sudo, clarify, interrupt all work through gateway

Cut 4: Opt-In and Rollout

Entry points:

  • hermes --tui
  • HERMES_EXPERIMENTAL_TUI=1
  • display.experimental_tui: true
  • /tui, /tui on, /tui off, /tui status

Behavior:

  • /tui starts gateway if needed and attaches
  • failed attach falls back to PT mode with explicit error text
  • /tui off disables auto-launch only

Rollout:

  1. internal opt-in
  2. external opt-in beta
  3. default-on after checks pass
  4. remove PT path later

Acceptance Checks

  • runtime: no PT import in controller/gateway path
  • state: same config/profile/session continuity
  • commands: slash command registry remains hermes_cli/commands.py
  • permissions: approval/sudo/clarify protocol round-trip
  • streaming: incremental assistant and tool updates
  • opt-in: flag/env/config/slash command share one launch path

Test Commands

  • python -m pytest tests/tui_gateway/test_controller.py -q
  • python -m pytest tests/tui_gateway/test_protocol.py tests/tui_gateway/test_server_flow.py -q
  • python -m pytest tests/tui_gateway/test_permissions_roundtrip.py -q
  • python -m pytest tests/hermes_cli/test_tui_opt_in.py -q
  • cd ui-tui && npm run build
  • cd ui-tui && npm run test

Prompt Toolkit to Ink Migration Plan

Date: 2026-04-01

Scope

This is a refactor in the same repo.

  • no new repo
  • no runtime rewrite
  • no messaging gateway reuse for terminal UI

Current Environment

Interactive Hermes today is prompt_toolkit plus rich inside cli.py.

Current structure:

  • PT app shell and input handling in cli.py
    • Application
    • KeyBindings
    • TextArea
    • patch_stdout
  • queue-based control flow in cli.py
    • _pending_input
    • _interrupt_queue
  • approval and sudo callbacks in tools/terminal_tool.py
    • _approval_callback
    • _sudo_password_callback
  • core runtime in run_agent.py
    • AIAgent.run_conversation()
    • AIAgent.chat()

Current issue:

  • UI framework logic and runtime control flow are mixed in one path.
  • Tool prompt routing depends on PT callback globals.
  • Replacing UI without changing runtime is harder than it should be.

Ideal Environment

Interactive Hermes is Ink UI plus a local TUI gateway.

Target model:

  • Python runtime remains the source of truth.
  • UI talks to tui_gateway over stdio JSON-RPC.
  • tui_gateway talks to AIAgent.
  • no direct UI to AIAgent coupling.

Target compatibility:

  • same ~/.hermes/.env
  • same ~/.hermes/config.yaml
  • same ~/.hermes/state.db
  • same profile behavior through HERMES_HOME

How To Get There

Use three delivery cuts and one switch cut.

Cut 1: Headless Runtime Controller

Goal:

  • separate runtime control from PT.

Add:

  • tui_gateway/controller.py
  • tui_gateway/session_state.py
  • tui_gateway/events.py

Change:

  • run_agent.py callback wiring needed by controller
  • cli.py compatibility calls into controller

Done when:

  • controller supports create/resume/prompt/interrupt/cancel
  • controller path imports no PT modules
  • tool progress and assistant deltas are typed events

Cut 2: Local TUI Gateway

Goal:

  • add stable protocol boundary for UI.

Add:

  • tui_gateway/protocol.py
  • tui_gateway/server.py
  • tui_gateway/entry.py
  • tui_gateway/__init__.py

Protocol methods:

  • session.create
  • session.resume
  • session.list
  • session.interrupt
  • session.cancel
  • prompt.submit
  • approval.respond
  • sudo.respond
  • clarify.respond

Protocol events:

  • message.delta
  • tool.progress
  • approval.requested
  • sudo.requested
  • clarify.requested
  • error

Done when:

  • a simple client can complete one full prompt cycle over stdio JSON-RPC

Cut 3: Ink UI

Goal:

  • usable clone flow through gateway.

Add:

  • ui-tui/package.json
  • ui-tui/src/main.tsx
  • ui-tui/src/gatewayClient.ts
  • ui-tui/src/state/store.ts
  • ui-tui/src/components/Transcript.tsx
  • ui-tui/src/components/Composer.tsx
  • ui-tui/src/components/StatusBar.tsx
  • ui-tui/src/components/ApprovalModal.tsx
  • ui-tui/src/components/SudoPrompt.tsx
  • ui-tui/src/components/ClarifyPrompt.tsx

Change:

  • tools/terminal_tool.py adapters for gateway request/response prompt routing

Done when:

  • user can chat, run tools, approve, deny, clarify, interrupt, and continue

Cut 4: Opt-In Switch and Rollout

Goal:

  • ship without forced cutover.

Entry points:

  • hermes --tui
  • HERMES_EXPERIMENTAL_TUI=1
  • display.experimental_tui: true
  • /tui, /tui on, /tui off, /tui status

Behavior:

  • /tui starts gateway if needed, then attaches
  • attach failure returns to PT mode with clear error text
  • /tui off disables auto-launch only

Rollout sequence:

  1. internal opt-in
  2. external opt-in beta
  3. default-on after checks pass
  4. PT path removal later

Acceptance Checks

  • runtime
    • no PT import in controller or gateway path
    • deterministic interrupt/cancel
  • state
    • same config, profile, and session continuity
  • commands
    • slash command registry remains centralized in hermes_cli/commands.py
  • permissions
    • approval, sudo, clarify round-trip through protocol
  • streaming
    • incremental assistant and tool updates
  • opt-in
    • flag, env, config, and slash command trigger the same launch path

Test Commands

  • python -m pytest tests/tui_gateway/test_controller.py -q
  • python -m pytest tests/tui_gateway/test_protocol.py tests/tui_gateway/test_server_flow.py -q
  • python -m pytest tests/tui_gateway/test_permissions_roundtrip.py -q
  • python -m pytest tests/hermes_cli/test_tui_opt_in.py -q
  • cd ui-tui && npm run build
  • cd ui-tui && npm run test

Non-Goals

  • no ACP extraction work as prerequisite
  • no new repository split
  • no direct UI to AIAgent coupling
  • no PT feature parity before gateway path is stable

Prompt Toolkit to Ink Migration Plan

Date: 2026-04-01

Scope

This is a refactor in the same repo.

  • no new repo
  • no runtime rewrite
  • no messaging gateway reuse for terminal UI

Current Environment (As-Is)

Interactive Hermes today is prompt_toolkit plus rich inside cli.py.

Facts from code:

  • PT imports and app shell in cli.py
    • Application, KeyBindings, TextArea, patch_stdout
  • PT queue control path in cli.py
    • _pending_input for normal input
    • _interrupt_queue for input while agent is running
  • tool approval and sudo prompts use CLI callbacks in tools/terminal_tool.py
    • _sudo_password_callback
    • _approval_callback
  • core agent runtime is Python in run_agent.py
    • AIAgent.run_conversation()
    • AIAgent.chat()

Current coupling problem:

  • UI framework and runtime control flow are mixed in cli.py.
  • Tool prompts depend on CLI callback globals.
  • This blocks clean UI replacement.

Ideal Environment (To-Be)

Interactive Hermes is Ink UI plus a local TUI gateway.

Runtime

  • AIAgent stays in Python.
  • Tool execution stays in Python.
  • Session storage and config remain unchanged.

Boundary

  • new tui_gateway process over stdio JSON-RPC
  • UI talks only to gateway
  • gateway talks to AIAgent

UI

  • Node/TypeScript Ink app
  • transcript, composer, status, approvals, clarify, sudo, interrupt

Compatibility

Use existing Hermes state and config:

  • ~/.hermes/.env
  • ~/.hermes/config.yaml
  • ~/.hermes/state.db
  • profile behavior via HERMES_HOME

How To Get There

Use three implementation cuts plus one switch cut.

Cut 1: Headless Runtime Controller

Goal: separate runtime control flow from PT.

Add:

  • tui_gateway/controller.py
  • tui_gateway/session_state.py
  • tui_gateway/events.py

Change:

  • run_agent.py only for callback wiring needed by controller
  • cli.py to call controller APIs in compatibility mode

Done when:

  • controller can create/resume/prompt/interrupt/cancel without importing PT
  • tool progress and assistant deltas are emitted as typed events

Cut 2: Local TUI Gateway

Goal: protocol boundary between UI and runtime.

Add:

  • tui_gateway/protocol.py
  • tui_gateway/server.py
  • tui_gateway/entry.py
  • tui_gateway/__init__.py

Protocol methods:

  • session.create
  • session.resume
  • session.list
  • session.interrupt
  • session.cancel
  • prompt.submit
  • approval.respond
  • sudo.respond
  • clarify.respond

Protocol events:

  • message.delta
  • tool.progress
  • approval.requested
  • sudo.requested
  • clarify.requested
  • error

Done when:

  • a simple client can run one full prompt cycle over stdio JSON-RPC

Cut 3: Ink UI

Goal: usable clone experience through gateway.

Add:

  • ui-tui/package.json
  • ui-tui/src/main.tsx
  • ui-tui/src/gatewayClient.ts
  • ui-tui/src/state/store.ts
  • ui-tui/src/components/Transcript.tsx
  • ui-tui/src/components/Composer.tsx
  • ui-tui/src/components/StatusBar.tsx
  • ui-tui/src/components/ApprovalModal.tsx
  • ui-tui/src/components/SudoPrompt.tsx
  • ui-tui/src/components/ClarifyPrompt.tsx

Change:

  • tools/terminal_tool.py adapters so prompts round-trip through gateway path, not PT-only callbacks

Done when:

  • user can chat, run tools, approve, deny, clarify, interrupt, and continue

Cut 4: Opt-In Switch and Rollout

Goal: ship safely without forced cutover.

Entry points:

  • hermes --tui
  • HERMES_EXPERIMENTAL_TUI=1
  • display.experimental_tui: true
  • /tui, /tui on, /tui off, /tui status in legacy CLI

Behavior:

  • /tui starts gateway if needed, then attaches
  • attach failure returns to PT mode with clear error text
  • /tui off disables auto-launch only

Rollout:

  1. internal opt-in
  2. external opt-in beta
  3. default-on only after acceptance checks pass
  4. PT path removal later

Acceptance Checks

  • runtime
    • no PT import in controller or gateway path
    • deterministic interrupt/cancel
  • state
    • same config, profile, and session continuity
  • commands
    • slash command registry stays centralized in hermes_cli/commands.py
  • permissions
    • approval, sudo, clarify all round-trip through protocol
  • streaming
    • incremental assistant and tool updates
  • opt-in
    • flag, env, config, and slash command all trigger same launch path

Test Commands

  • python -m pytest tests/tui_gateway/test_controller.py -q
  • python -m pytest tests/tui_gateway/test_protocol.py tests/tui_gateway/test_server_flow.py -q
  • python -m pytest tests/tui_gateway/test_permissions_roundtrip.py -q
  • python -m pytest tests/hermes_cli/test_tui_opt_in.py -q
  • cd ui-tui && npm run build
  • cd ui-tui && npm run test

Non-Goals

  • no ACP extraction work as prerequisite
  • no new repository split
  • no direct UI to AIAgent coupling
  • no PT feature parity before gateway path is stable

Ink Gateway TUI Migration Plan

Date: 2026-04-01

Goal

Replace Hermes' interactive prompt_toolkit CLI with a React terminal UI built on Ink, while keeping the Python agent and tool runtime in place.

The new design should:

  • remove prompt_toolkit from the interactive path entirely
  • keep AIAgent, tool execution, and session logic in Python
  • introduce a transport-neutral local UI gateway between backend and frontend
  • use stock Ink first, not a Claude Code renderer transplant
  • keep using the same Hermes config, profile, skills, memory, and session storage model

Decision Summary

Hermes should not evolve the current prompt_toolkit shell. The replacement architecture is:

  1. Python backend session server
  2. local gateway transport over stdio JSON-RPC
  3. Node/TypeScript Ink TUI frontend

This intentionally uses a dedicated local TUI gateway and keeps acp_adapter unchanged.

The new TUI is a new shell, not a new runtime.

Compatibility Requirements

From the existing Hermes docs and setup flows, the new TUI should continue to use:

  • the same ~/.hermes/.env provider/auth configuration
  • the same ~/.hermes/config.yaml settings model
  • the same ~/.hermes/state.db session store
  • the same HERMES_HOME profile layout and isolation rules
  • the same skills, memories, and slash-command registry already shared across Hermes surfaces

The migration should not create:

  • a separate TUI-only config file
  • a separate TUI-only session database
  • a separate prompt assembly path with drift from existing Hermes runtime behavior

Why This Shape

The current interactive CLI is too coupled to prompt_toolkit to incrementally clean up in place:

  • cli.py mixes input handling, rendering, approvals, clarify flows, voice, queues, and agent orchestration
  • tools/terminal_tool.py assumes UI callbacks installed by the CLI
  • the current event model is built around PT queues and threads, not a transport-neutral session API

At the same time, a full port to Claude Code's custom renderer is the wrong first move:

  • Claude Code's TUI stack is not just Ink; it includes a product-coupled renderer fork and app bootstrap assumptions
  • Hermes does not need that complexity to reach a good first-party TUI
  • stock Ink is enough to validate the UI model and close the biggest UX gap first

Operationally, a Node/TypeScript frontend is acceptable here because Hermes already ships with a Node-aware install story and already supports Node-based surfaces in the wider product.

Non-Goals

  • reusing the messaging gateway as the TUI transport
  • preserving prompt_toolkit compatibility
  • matching Claude Code internals one-for-one
  • rewriting Hermes' core agent or tool runtime in Node

High-Level Architecture

The new interactive stack has three layers:

  1. python runtime Owns AIAgent, tool execution, session state, approvals, interrupts, and filesystem/terminal tools.
  2. ui gateway A local protocol server that exposes Hermes sessions as typed requests, responses, and events.
  3. ink tui A React terminal app that renders transcript, composer, status, approvals, tool cards, and slash-command UX.

Suggested process model:

hermes
  1. launch ink tui (node)
  2. spawn python ui gateway over stdio
  3. create/resume session
  4. exchange JSON-RPC requests + streaming events

Why Not The Existing Messaging Gateway

The messaging gateway solves a different problem:

  • multi-platform message routing
  • user authorization and pairing
  • per-platform delivery behavior
  • long-running bot process management

That stack is useful as architecture background, but it is the wrong seam for a local terminal app.

The ACP adapter demonstrates the right boundary shape:

  • backend runtime behind a protocol boundary
  • callback/event bridging
  • permission round-trips
  • explicit session lifecycle

The new local UI gateway should target a Hermes TUI protocol directly, not an editor protocol.

ACP Isolation Strategy

Do not use ACP extraction as a prerequisite.

Instead:

  1. build tui_gateway directly around AIAgent
  2. keep acp_adapter/* untouched during early migration
  3. allow shared-runtime refactors later only if they reduce real maintenance cost

Reasons:

  • ACP payloads are editor-shaped and add translation overhead
  • ACP-first migration adds scope and time before the new TUI ships
  • owner direction favors a fast clone path with gateway indirection, not transport unification work

Proposed Backend Split

Extract the following concerns out of cli.py:

  1. session controller A headless controller for create, resume, prompt, interrupt, cancel, and slash-command dispatch.
  2. event bridge Converts agent callbacks and tool progress into structured UI events.
  3. permission bridge Converts dangerous-command approval, sudo prompts, and clarify requests into request/response interactions.
  4. presentation adapters Optional formatting helpers for transcript items and tool previews, without owning terminal rendering.
  5. gateway adapter A thin request/event layer for tui_gateway over stdio JSON-RPC.

The backend must stop depending on a terminal UI framework for control flow.

Shared Runtime Invariants

The backend remains the source of truth for:

  • prompt assembly
  • Honcho/memory synchronization
  • tool dispatch
  • approval policy
  • slash-command execution
  • session transitions

The frontend should render protocol state, not own core agent behavior.

In particular, the new TUI must not introduce UI-side blocking work into the turn path. Context, memory, Honcho prefetch, and similar backend concerns should stay behind the runtime boundary and preserve Hermes' existing caching and async-prefetch behavior.

Proposed Transport

Start with stdio JSON-RPC.

Reasons:

  • local CLI startup is simple
  • process ownership is clear
  • no port management

WebSocket can be added later if Hermes wants:

  • remote terminal clients
  • browser UI
  • multiple concurrent viewers

But it should not be the first transport.

Platform And Toolset Strategy

The new UI should run Hermes in a dedicated tui platform mode.

That mode should:

  • share most behavioral semantics with the current interactive CLI
  • reuse the canonical slash-command registry rather than fork it
  • preserve session continuity with other Hermes surfaces where the shared state model already supports it
  • avoid editor-specific payload conventions in the TUI protocol

Toolset strategy:

  • start from current interactive CLI capabilities
  • only introduce a dedicated hermes-tui toolset if the transport boundary proves it is needed
  • keep transport constraints out of tool business logic as much as possible

Protocol Shape

The TUI protocol should be explicit and event-driven.

Core requests:

  • session.create
  • session.resume
  • session.list
  • session.interrupt
  • session.cancel
  • session.set_cwd
  • prompt.submit
  • command.run
  • approval.respond
  • sudo.respond
  • clarify.respond

Core events:

  • session.state
  • message.start
  • message.delta
  • message.complete
  • thinking.delta
  • tool.started
  • tool.progress
  • tool.completed
  • approval.requested
  • sudo.requested
  • clarify.requested
  • error

Design rule: every user-visible interactive state in the new TUI must come from protocol state, not local UI guesswork.

Ink Frontend Scope

The first Ink frontend only needs a narrow set of surfaces:

  • transcript view
  • input composer
  • status/footer bar
  • slash-command picker/help
  • approval modal
  • sudo prompt
  • clarify prompt
  • tool activity cards

Do not start with:

  • mouse-heavy interaction
  • custom selection model
  • custom renderer internals
  • Claude-style terminal instrumentation

Those can come later if real gaps appear.

Migration Phases

Phase 1: Headless Runtime Extraction

Goal: make Hermes usable without prompt_toolkit.

Work:

  • introduce a backend session/controller module
  • move PT-specific queues and rendering concerns out of agent flow
  • replace direct CLI callback assumptions with abstract request/response hooks
  • isolate slash-command execution from the PT shell
  • introduce a platform="tui" runtime path without forking core agent logic

Exit criteria:

  • a non-PT backend can run a prompt, stream progress, request approval, and return a final response

Phase 2: Local UI Gateway

Goal: expose the backend over stdio JSON-RPC.

Work:

  • create a ui_gateway package or equivalent module group
  • model session lifecycle and event streaming
  • implement cancel/interrupt behavior
  • adapt terminal approval and sudo flow into transport messages
  • keep config, profile, and session storage identical to existing Hermes surfaces

Exit criteria:

  • a minimal client can drive a full Hermes session over stdio without importing cli.py

Phase 3: Ink MVP

Goal: ship a working Hermes TUI without prompt_toolkit.

Work:

  • create a Node/TS package for the TUI
  • connect to the Python gateway
  • render transcript + composer + status
  • support approvals, clarify prompts, and slash commands
  • preserve interrupt-and-redirect behavior for active runs

Exit criteria:

  • Hermes can be used end-to-end from the new TUI for normal chat and tool use

Phase 4: Feature Parity

Goal: close the biggest regressions from the legacy CLI.

Work:

  • port session picker/resume UX
  • port tool previews and long-running command status
  • port config-aware commands
  • port voice or explicitly defer it behind a non-blocking boundary

Exit criteria:

  • daily-driver workflows no longer require the PT CLI

Phase 5: Cutover And Deletion

Goal: make the new TUI the default interactive path.

Work:

  • switch hermes interactive startup to the Ink client
  • keep legacy PT path only behind a temporary fallback flag if needed
  • delete PT-specific code after a short stabilization window

Exit criteria:

  • prompt_toolkit is no longer part of the main interactive CLI

File-Level Refactor Targets

Initial hot spots:

  • cli.py
  • tools/terminal_tool.py
  • model_tools.py
  • run_agent.py
  • hermes_cli/commands.py

Expected pattern:

  • avoid importing PT code into the new backend path
  • move any UI-specific formatting behind protocol events or thin adapters

First Implementation Slices (PR Plan)

Keep early PRs narrow and mergeable. Do not start with a large branch that rewrites cli.py end-to-end.

  1. PR-1: headless session controller
    • add a transport-neutral controller around AIAgent for create/resume/prompt/interrupt/cancel
    • no UI, no PT dependencies
  2. PR-2: local ui gateway (stdio json-rpc)
    • add ui_gateway process entry
    • implement protocol requests/events for one full prompt cycle
  3. PR-3: ink shell bootstrap
    • add Node/TS package with gateway client
    • render transcript + composer + status + streaming deltas
  4. PR-4: interactive controls parity
    • approvals, sudo, clarify flows
    • interrupt-and-redirect and command routing
  5. PR-5: startup switch + fallback flag
    • add explicit opt-in startup flag for Ink path (HERMES_EXPERIMENTAL_TUI=1 or equivalent)
    • add CLI/config opt-in controls and /tui command entrypoint in legacy CLI
    • keep PT path behind a temporary env/flag gate during stabilization
  6. PR-6: parity hardening and PT deletion
    • close remaining UX gaps from legacy CLI
    • remove PT path after stability window

Concrete File Plan

Use fixed locations so contributors do not invent parallel structures.

PR-1 files:

  • add tui_gateway/controller.py
  • add tui_gateway/session_state.py
  • add tui_gateway/events.py
  • update run_agent.py only where callback wiring is needed
  • update cli.py only to call controller entry points in compatibility mode

PR-2 files:

  • add tui_gateway/protocol.py
  • add tui_gateway/server.py
  • add tui_gateway/entry.py
  • add tui_gateway/__init__.py
  • add tests/tui_gateway/test_protocol.py
  • add tests/tui_gateway/test_server_flow.py

PR-3 files:

  • add ui-tui/package.json
  • add ui-tui/src/main.tsx
  • add ui-tui/src/gatewayClient.ts
  • add ui-tui/src/state/store.ts
  • add ui-tui/src/components/Transcript.tsx
  • add ui-tui/src/components/Composer.tsx
  • add ui-tui/src/components/StatusBar.tsx

PR-4 files:

  • add ui-tui/src/components/ApprovalModal.tsx
  • add ui-tui/src/components/SudoPrompt.tsx
  • add ui-tui/src/components/ClarifyPrompt.tsx
  • update tools/terminal_tool.py to use gateway request/response adapters instead of PT-specific assumptions
  • add tests/tui_gateway/test_permissions_roundtrip.py

PR-5 files:

  • update hermes_cli/main.py startup selection for --tui and env/config flags
  • update hermes_cli/commands.py with /tui commands
  • update cli.py command dispatch to launch/attach behavior
  • add tests/hermes_cli/test_tui_opt_in.py

PR-6 files:

  • remove PT-only paths from cli.py once parity checks pass
  • remove obsolete PT wiring helpers
  • update docs and command help text

If path names change, keep one module per role and avoid duplicate gateway implementations.

Protocol Envelope (v0)

Use one JSON-RPC envelope shape for all gateway traffic.

Request:

{
  "jsonrpc": "2.0",
  "id": "req-123",
  "method": "prompt.submit",
  "params": {
    "session_id": "sess-1",
    "text": "hello"
  }
}

Event notification:

{
  "jsonrpc": "2.0",
  "method": "event",
  "params": {
    "type": "message.delta",
    "session_id": "sess-1",
    "payload": {
      "text": "hi"
    }
  }
}

Error:

{
  "jsonrpc": "2.0",
  "id": "req-123",
  "error": {
    "code": 4001,
    "message": "session not found"
  }
}

Protocol rules:

  • all event ordering is per-session FIFO
  • ids are opaque strings
  • unknown event types are ignored by clients and logged
  • protocol version is pinned in tui_gateway/protocol.py

Acceptance Checks Per Phase

Each phase should ship with explicit checks:

  • runtime
    • prompt executes end-to-end without importing prompt_toolkit
    • interrupt and cancel are deterministic
  • state continuity
    • same HERMES_HOME, config.yaml, state.db, and profile behavior as existing Hermes surfaces
  • commands
    • slash-command resolution uses shared registry (hermes_cli/commands.py)
  • permissions
    • dangerous command approval, sudo prompt, and clarify prompt all round-trip through protocol events
  • streaming
    • message/tool progress events stream incrementally; no UI-side polling loop for core turn output
  • opt-in controls
    • --tui, env flag, config toggle, and /tui commands all resolve to the same launch behavior
    • failures fall back to PT mode with explicit error output

Test Commands Per PR

PR-1:

  • python -m pytest tests/tui_gateway/test_controller.py -q

PR-2:

  • python -m pytest tests/tui_gateway/test_protocol.py tests/tui_gateway/test_server_flow.py -q

PR-3:

  • cd ui-tui && npm run build
  • cd ui-tui && npm run test

PR-4:

  • python -m pytest tests/tui_gateway/test_permissions_roundtrip.py -q
  • cd ui-tui && npm run test

PR-5:

  • python -m pytest tests/hermes_cli/test_tui_opt_in.py -q

PR-6:

  • python -m pytest tests/ -q

Opt-In UX Surface

Expose TUI opt-in through user-facing TUI language, not transport language.

Entry points:

  • startup flag: hermes --tui
  • env flag: HERMES_EXPERIMENTAL_TUI=1
  • config toggle: display.experimental_tui: true
  • slash command in legacy CLI:
    • /tui (launch/attach)
    • /tui on (persist opt-in)
    • /tui off (disable auto-launch)
    • /tui status (show mode + process/attach state)

Behavior:

  • if /tui is called and the local TUI gateway is not running, start it and attach
  • if already running, attach/reuse session
  • on startup/attach failure, print clear error and stay in PT mode
  • /tui off disables future auto-launch; it does not terminate active sessions unless requested

Rollout And Rollback

Rollout should be staged:

  1. internal opt-in (HERMES_EXPERIMENTAL_TUI=1 or equivalent)
  2. external opt-in beta (still flag-gated, PT remains default)
  3. default-on with PT fallback still available, only after acceptance checks are green
  4. PT removal after a short stability window

Rollback path must remain simple until PT deletion:

  • one switch to restore legacy interactive startup
  • no data migration required between TUI and PT modes (shared state model)

Main Risks

  1. cli.py currently owns more state than it appears to. Extraction will uncover hidden coupling.
  2. Approval and sudo flows are global/callback-driven today and need per-session protocol state.
  3. Long-running tool output may currently assume terminal-local behavior that has to be normalized before transport.
  4. Voice mode may carry PT assumptions and should be treated as optional during the first cut.
  5. If the frontend demands behavior beyond stock Ink, the team may need to introduce custom terminal primitives later.

Recommendation

Start with stock Ink and a direct tui_gateway over stdio JSON-RPC.

Do not:

  • refactor prompt_toolkit forward
  • route the terminal UI through the messaging gateway
  • begin by vendoring Claude Code's renderer

The shortest path to a good Hermes TUI is:

  1. extract headless backend control flow
  2. expose it over stdio JSON-RPC
  3. build the TUI in Ink
  4. only customize deeper terminal behavior after real product pressure appears

Success Criteria

This migration succeeds if Hermes can:

  • start an interactive session without prompt_toolkit
  • stream assistant and tool activity live into an Ink UI
  • handle approvals, clarify requests, sudo prompts, and interrupts cleanly
  • preserve the existing Python agent/tool runtime
  • preserve existing Hermes config, profile, and session continuity expectations
  • preserve shared slash-command semantics instead of inventing a second command surface
  • avoid adding new blocking UI-driven work into the prompt path
  • make the legacy PT shell deletable rather than permanent