系列第六篇。本文解析 Hermes 三种 UI 表面的分层架构:TypeScript/Python 职责边界如何防止功能漂移,PTY 桥接比直连更优的深层逻辑,以及皮肤引擎的数据驱动原则在实践中意味着什么。
Hermes 有三个 UI 表面:
cli.py,基于 prompt_toolkit):直接在终端里运行的交互界面ui-tui/,基于 Ink/React):用 hermes --tui 激活的全屏终端界面,有更丰富的布局和交互hermes_cli/web_server.py):用 hermes dashboard 启动的浏览器界面,提供配置管理、会话浏览、聊天功能如果为三个界面分别实现"AI 对话"功能,会怎样?假设你修复了一个 context compression 的 bug——你需要在 CLI、TUI、Web 三处分别更新对应的逻辑,而且你要保证三处的行为一致。假设你添加了一个新工具——它的调用结果需要在三个界面里都正确展示。随着功能增加,三个实现之间的行为偏差会越来越难以控制。
Hermes 的选择是把"唯一真相"(AIAgent + 工具 + 会话)锁在 Python 里,让所有 UI 表面只做展示。这个决策在技术上并不简单,但它保证了功能一致性的可维护性。
AGENTS.md 明确描述了 TUI 的进程模型:
hermes --tui
└─ Node (Ink) ──stdio JSON-RPC── Python (tui_gateway)
│ └─ AIAgent + tools + sessions
└─ renders transcript, composer, prompts, activity
TypeScript(Ink)拥有屏幕。Python 拥有会话、工具调用、模型 API。
这个边界在实践中最重要的效果是防止功能漂移。所谓功能漂移,是指某个功能在一个界面里做了改进或修复,但另一个界面的实现没有同步更新,随时间积累形成行为差异。
具体例子:假设你想为 Hermes 添加一个"工具调用失败时自动重试"的机制。
如果 TUI 有自己的工具调用逻辑,你需要在 Python(run_agent.py)实现一次,在 TypeScript(tui_gateway)实现一次——或者你可能意识到只在 Python 里实现了,却忘了 TUI 也需要同步。
但在当前架构下,工具调用逻辑完全在 Python 的 run_agent.py 里,TUI 只负责显示工具调用的进度事件(tool.start/progress/complete)。自动重试机制加在 Python 里,TUI 立刻受益——不需要任何额外工作。
AGENTS.md 对 Dashboard 的 /chat 页面说得更直接:
Do not re-implement the primary chat experience in React. The main transcript,
composer/input flow (including slash-command behavior), and PTY-backed terminal
belong to the embedded hermes --tui — anything new you add to Ink shows up in
the dashboard automatically.