The Hermes Agent TUI is a high-fidelity terminal interface built using React Ink. It provides a rich, interactive experience that includes real-time streaming of assistant reasoning (Chain-of-Thought), live tool execution tracking, markdown rendering, and a robust command system. Unlike the default prompt_toolkit CLI, the TUI operates on a client-server model where a Node.js frontend communicates with a Python-based JSON-RPC gateway.
The TUI architecture separates the rendering logic from the agent's execution environment. This allows the UI to remain responsive even during heavy computation or long-running tool executions.
This diagram illustrates the bridge between the React-based UI components and the Python gateway services.
Sources: ui-tui/src/app.tsx9-25 tui_gateway/server.py128-135 ui-tui/src/app/createGatewayEventHandler.ts45-58 ui-tui/src/app/useMainApp.ts152-211
The TUI uses JSON-RPC 2.0 over standard input/output. The Python gateway redirects standard print() calls to stderr to prevent protocol corruption, reserving stdout exclusively for JSON messages.
session.create or session.resume via the GatewayClient ui-tui/src/app/useMainApp.ts103-150message.delta (for live text) and tool.start (for execution tracking). These are handled by createGatewayEventHandler ui-tui/src/app/createGatewayEventHandler.ts45-58/ are handled by a dedicated _SlashWorker subprocess that maintains its own HermesCLI instance to avoid blocking the main dispatcher tui_gateway/server.py155-171_LONG_HANDLERS like cli.exec or session.resume) are routed to a ThreadPoolExecutor (_pool) so that inbound RPCs like approval.respond and session.interrupt can still be processed immediately tui_gateway/server.py184-199tui_gateway/server.py)The server manages the lifecycle of agent sessions and acts as a bridge to the AIAgent.
_SlashWorker: A persistent subprocess that executes CLI commands and returns structured output tui_gateway/server.py155-171_stdout_lock for serializing and sending protocol messages back to the React frontend tui_gateway/server.py141sys.excepthook and threading.excepthook to capture gateway crashes in tui_gateway_crash.log, as standard output is reserved for the JSON pipe tui_gateway/server.py53-123The TUI features a sophisticated input area supporting multi-line editing and history.
TextInput: A high-performance React component handling terminal-specific input events, including bracketed paste detection, multi-click selection, and UTF-8 grapheme-aware cursor movement ui-tui/src/components/textInput.tsx28-242lineNav: Pure logic for navigating logical lines (Up/Down) within a multi-line input buffer ui-tui/src/components/textInput.tsx245-265inputVisualHeight to ensure the UI doesn't flicker during multi-line expansion ui-tui/src/components/appLayout.tsx17-22ToolTrail)One of the TUI's primary advantages is the visualization of the agent's internal monologue and tool usage.
StreamingAssistant: Renders the current active turn, showing the "Thinking" spinner and live reasoning text ui-tui/src/components/appLayout.tsx37SubagentAccordion: Displays a tree of tool executions and subagent delegation, including hotness-based color coding for active branches ui-tui/src/components/thinking.tsx280-300Spinner: Provides visual feedback using braille-based animations (e.g., helix, breathe, cascade) during LLM generation or tool execution ui-tui/src/components/thinking.tsx153-173LiveTodoPanel: Displays real-time task progress (Pending/In-Progress/Completed) directly beneath the user message that triggered them ui-tui/src/components/appLayout.tsx37Sources: ui-tui/src/components/thinking.tsx1-173 ui-tui/src/components/appLayout.tsx138-169
The following diagram traces how a message moves from the LLM through the system to be rendered as Markdown in the TUI.
Sources: ui-tui/src/app/createGatewayEventHandler.ts45-58 ui-tui/src/components/messageLine.tsx1-20
| Feature | Default CLI (prompt_toolkit) | TUI (--tui) |
|---|---|---|
| Rendering Engine | Procedural ANSI | React-based Virtual DOM (Ink) |
| Reasoning (CoT) | Hidden or static block | Live streaming animation ui-tui/src/components/thinking.tsx153-173 |
| Tool Execution | Sequential log lines | Interactive SubagentAccordion tree ui-tui/src/components/thinking.tsx280-300 |
| Multi-line Input | Basic | Full editor with UTF-8 awareness ui-tui/src/components/textInput.tsx83-113 |
| Architecture | Single Python process | Node.js Client + Python Gateway tui_gateway/server.py184-199 |
| Mouse Support | Limited | Native (Scrolling, Selection, Drag) ui-tui/src/app/useMainApp.ts152-186 |
Sources: ui-tui/src/app/useMainApp.ts152-211 tui_gateway/server.py184-199
To maintain performance with long conversations, the TUI uses a virtualized history system.
useVirtualHistory: Calculates which messages are currently in the viewport to avoid rendering thousands of Ink components simultaneously ui-tui/src/app/useMainApp.ts33ScrollBox: A component from @hermes/ink that manages the terminal viewport and provides handles for programmatic scrolling (e.g., stickyScroll to follow new messages) ui-tui/src/components/appLayout.tsx183-194TranscriptScrollbar: Renders a visual scroll indicator synced with the ScrollBox state ui-tui/src/components/appChrome.tsx28The Md and StreamingMd components parse Markdown strings and map them to Ink components.
StreamingMd: Optimizes rendering by splitting at the last stable block boundary so only the in-flight tail re-tokenizes ui-tui/src/components/messageLine.tsx1-20The TUI includes a status bar (appChrome.tsx) that provides real-time metrics and feedback.
FaceTicker: Displays a rotating "kaomoji" or "braille" spinner and the duration of the current turn ui-tui/src/components/appChrome.tsx121-165ctxBarColor) showing current token usage relative to the model's context limit ui-tui/src/components/appChrome.tsx167-183GoodVibesHeart: A visual "thank you" animation triggered by positive user feedback ui-tui/src/components/appChrome.tsx28Sources: ui-tui/src/components/appChrome.tsx1-183 ui-tui/src/components/appLayout.tsx138-181
Refresh this wiki
This wiki was recently refreshed. Please wait 4 days to refresh again.