diff --git a/docs/plans/2026-04-01-ink-gateway-tui-migration-plan.md b/docs/plans/2026-04-01-ink-gateway-tui-migration-plan.md
index 5692c5e7a8..9050d2c0c6 100644
--- a/docs/plans/2026-04-01-ink-gateway-tui-migration-plan.md
+++ b/docs/plans/2026-04-01-ink-gateway-tui-migration-plan.md
@@ -1,1170 +1,108 @@
-# TUI Refactor: Current to Ideal
+# Ink Gateway TUI Migration — Post-mortem
-Date: 2026-04-01
+Planned: 2026-04-01 · Delivered: 2026-04 · Status: shipped, PT path still present
-## Scope
+## What Shipped
-- same repo refactor
-- keep Python runtime
-- replace PT-based interactive shell
-- add Ink UI through a local gateway
+Three layers, same repo, Python runtime unchanged.
-## 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:
-
-```text
-hermes
- 1. launch ink tui (node)
- 2. spawn python ui gateway over stdio
- 3. create/resume session
- 4. exchange JSON-RPC requests + streaming events
+```
+ui-tui (Node/TS) ──stdio JSON-RPC──▶ tui_gateway (Py) ──▶ AIAgent (run_agent.py)
```
-## 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:
-
-```json
-{
- "jsonrpc": "2.0",
- "id": "req-123",
- "method": "prompt.submit",
- "params": {
- "session_id": "sess-1",
- "text": "hello"
- }
-}
+### Backend — `tui_gateway/`
+
+```
+tui_gateway/
+├── entry.py # subprocess entrypoint, stdio read/write loop
+├── server.py # everything: sessions dict, @method handlers, _emit
+├── render.py # stream renderer, diff rendering, message rendering
+├── slash_worker.py # subprocess that runs hermes_cli slash commands
+└── __init__.py
```
-Event notification:
+`server.py` owns the full runtime-control surface: session store (`_sessions: dict[str, dict]`), method registry (`@method("…")` decorator), event emitter (`_emit`), agent lifecycle (`_make_agent`, `_init_session`, `_wire_callbacks`), approval/sudo/clarify round-trips, and JSON-RPC dispatch.
-```json
-{
- "jsonrpc": "2.0",
- "method": "event",
- "params": {
- "type": "message.delta",
- "session_id": "sess-1",
- "payload": {
- "text": "hi"
- }
- }
-}
+Protocol methods (`@method(...)` in `server.py`):
+
+- session: `session.{create, resume, list, close, interrupt, usage, history, compress, branch, title, save, undo}`
+- prompt: `prompt.{submit, background, btw}`
+- tools: `tools.{list, show, configure}`
+- slash: `slash.exec`, `command.{dispatch, resolve}`, `commands.catalog`, `complete.{path, slash}`
+- approvals: `approval.respond`, `sudo.respond`, `clarify.respond`, `secret.respond`
+- config/state: `config.{get, set, show}`, `model.options`, `reload.mcp`
+- ops: `shell.exec`, `cli.exec`, `terminal.resize`, `input.detect_drop`, `clipboard.paste`, `paste.collapse`, `image.attach`, `process.stop`
+- misc: `agents.list`, `skills.manage`, `plugins.list`, `cron.manage`, `insights.get`, `rollback.{list, diff, restore}`, `browser.manage`
+
+Protocol events (`_emit(…)` → handled in `ui-tui/src/app/createGatewayEventHandler.ts`):
+
+- lifecycle: `gateway.{ready, stderr}`, `session.info`, `skin.changed`
+- stream: `message.{start, delta, complete}`, `thinking.delta`, `reasoning.{delta, available}`, `status.update`
+- tools: `tool.{start, progress, complete, generating}`, `subagent.{start, thinking, tool, progress, complete}`
+- interactive: `approval.request`, `sudo.request`, `clarify.request`, `secret.request`
+- async: `background.complete`, `btw.complete`, `error`
+
+### Frontend — `ui-tui/src/`
+
+```
+src/
+├── entry.tsx # node bootstrap: bootBanner → spawn python → dynamic-import Ink → render()
+├── app.tsx # wraps
+├── bootBanner.ts # raw-ANSI banner to stdout in ~2ms, pre-React
+├── gatewayClient.ts # JSON-RPC client over child_process stdio
+├── gatewayTypes.ts # typed RPC responses + GatewayEvent union
+├── theme.ts # DEFAULT_THEME + fromSkin
+│
+├── app/ # hooks + stores — the orchestration layer
+│ ├── uiStore.ts # nanostore: sid, info, busy, usage, theme, status…
+│ ├── turnStore.ts # nanostore: per-turn activity / reasoning / tools
+│ ├── turnController.ts # imperative singleton for stream-time operations
+│ ├── overlayStore.ts # nanostore: modal/overlay state
+│ ├── useMainApp.ts # top-level composition hook
+│ ├── useSessionLifecycle.ts # session.create/resume/close/reset
+│ ├── useSubmission.ts # shell/slash/prompt dispatch + interpolation
+│ ├── useConfigSync.ts # config.get + mtime poll
+│ ├── useComposerState.ts # input buffer, paste snippets, editor mode
+│ ├── useInputHandlers.ts # key bindings
+│ ├── createGatewayEventHandler.ts # event-stream dispatcher
+│ ├── createSlashHandler.ts # slash command router (registry + python fallback)
+│ └── slash/commands/ # core.ts, ops.ts, session.ts — TS-owned slash commands
+│
+├── components/ # AppLayout, AppChrome, AppOverlays, MessageLine, Thinking, Markdown, pickers, prompts, Banner, SessionPanel
+├── config/ # env, limits, timing constants
+├── content/ # charms, faces, fortunes, hotkeys, placeholders, verbs
+├── domain/ # details, messages, paths, roles, slash, usage, viewport
+├── protocol/ # interpolation, paste regex
+├── hooks/ # useCompletion, useInputHistory, useQueue, useVirtualHistory
+└── lib/ # history, messages, osc52, rpc, text
```
-Error:
+### CLI entry points — `hermes_cli/main.py`
-```json
-{
- "jsonrpc": "2.0",
- "id": "req-123",
- "error": {
- "code": 4001,
- "message": "session not found"
- }
-}
-```
+- `hermes --tui` → `node dist/entry.js` (auto-builds when `.ts`/`.tsx` newer than `dist/entry.js`)
+- `hermes --tui --dev` → `tsx src/entry.tsx` (skip build)
+- `HERMES_TUI_DIR=…` → external prebuilt dist (nix, distro packaging)
-Protocol rules:
+## Diverged From Original Plan
-- 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`
+| Plan | Reality | Why |
+|---|---|---|
+| `tui_gateway/{controller,session_state,events,protocol}.py` | all collapsed into `server.py` | no second consumer ever emerged, keeping one file cheaper than four |
+| `ui-tui/src/main.tsx` | split into `entry.tsx` (bootstrap) + `app.tsx` (shell) | boot banner + early python spawn wanted a pre-React moment |
+| `ui-tui/src/state/store.ts` | three nanostores (`uiStore`, `turnStore`, `overlayStore`) | separate lifetimes: ui persists, turn resets per reply, overlay is modal |
+| `approval.requested` / `sudo.requested` / `clarify.requested` | `*.request` (no `-ed`) | cosmetic |
+| `session.cancel` | dropped | `session.interrupt` covers it |
+| `HERMES_EXPERIMENTAL_TUI=1`, `display.experimental_tui: true`, `/tui on/off/status` | none shipped | `--tui` went from opt-in to first-class without an experimental phase |
-## Acceptance Checks Per Phase
+## Post-migration Additions (not in original plan)
-Each phase should ship with explicit checks:
+- **Async `session.create`** — returns sid in ~1ms, agent builds on a background thread, `session.info` broadcasts when ready; `_wait_agent()` gates every agent-touching handler via `_sess`
+- **`bootBanner`** — raw-ANSI logo painted to stdout at T≈2ms, before Ink loads; `` wipes it seamlessly when React mounts
+- **Selection uniform bg** — `theme.color.selectionBg` wired via `useSelection().setSelectionBgColor`; replaces SGR-inverse per-cell swap that fragmented over amber/gold fg
+- **Slash command registry** — TS-owned commands in `app/slash/commands/{core,ops,session}.ts`, everything else falls through to `slash.exec` (python worker)
+- **Turn store + controller split** — imperative singleton (`turnController`) holds refs/timers, nanostore (`turnStore`) holds render-visible state
-- `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
+## What's Still Open
-## 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
+- **PT path not deleted.** `cli.py` still has ~80 `prompt_toolkit` references; classic REPL is still the default when `--tui` is absent. The original plan's "Cut 4 · PT path removal later" hasn't happened.
+- **No config-file opt-in.** `HERMES_EXPERIMENTAL_TUI` and `display.experimental_tui` were never built; only the CLI flag exists. Fine for now — if we want "default to TUI", a single line in `main.py` flips it.