Skip to main content

Classic gateway client (initial migration)

Normal hermes --cli, hermes chat, hermes chat -q '…' -Q, and top-level hermes -z '…' use the gateway authority. Direct cli.main chat launches also hand off before constructing a local agent. Local bootstrap uses the canonical ensure lifecycle and a single-use private control ticket, not service installation. An explicit HERMES_TUI_GATEWAY_URL remains remote-only: failed connection or authentication never launches a local replacement.

The client prints the persisted Session: ID to stderr. Resume with hermes --cli chat --resume ID. Input is admitted with a fresh input_id; events and final replies come from the authority. /quit, /exit, /detach, EOF and closing the terminal detach only. /stop sends an execution-generation-fenced interrupt. Pending approvals display /approve ID once|deny|…; clarification uses /answer ID TEXT. The client uses the displayed prompt ID and its captured generation, not a locally reconstructed waiter. /discard ID acknowledges an admission lost across an owner restart (prompt.resolve_unknown). /branch [title], /model <model> [--provider name] and /compress [here [N] | <focus>] [--preview] are revision-fenced session.mutate operations (hermes_cli/gateway_mutations.py); /yolo [on|off] toggles this session's approval bypass on the owner (config.set key yolo). /title [name] (session.mutate rename), /undo [N] and /retry (session.mutate rewind; /undo puts the removed message back in the composer), /new [title] / /reset (session.create with this session's frozen launch request), /usage (the owner's report plus the last committed turn) and /tools (launch toolsets) run in the view (hermes_cli/gateway_chat_commands.py). Every other command goes to the owner as slash.exec: the reviewed reads run there and the rest print /x is not available on the shared gateway yet with a link to the command parity table, which lists what every local client does with every command.

One-shot stdout ends with the final reply. Without -Q, a resumed session first prints its stored history (user: / assistant: lines), and the reply is followed by the Resume this session with: block (see Launch options); -Q prints the reply alone. Exit status is 0 for a completed admission, 1 for failed execution/connection, 2 for unsupported frontend options, 3 for a pending control that requires interactive reattachment or a turn lost in a gateway crash (clear it with hermes sessions discard <id> --yes), and 130 for a keyboard detach. A pending control does not imply cancellation. -z no longer implicitly bypasses approval policy.

Launch options​

The gateway advertises its accepted creation parameters through runtime.describe (session_create.parameters, from gateway.session_policy.CREATE_FIELDS). The client sends a creation option only when it is advertised; a requested option an older gateway does not advertise is refused (Gateway does not support creation options: …). Caller cwd is sent when advertised; an old gateway lacking cwd support prints a warning that it uses its configured execution directory, while explicit --in rejects. A resume may repeat the exact creation flags the session was frozen with (scripts re-run one command line); any flag that would change the frozen route rejects.

Accepted and frozen into the session at creation (_POLICY in hermes_cli/gateway_chat.py): --model, --provider, --base-url, --api-key (memory-only, never persisted), --reasoning, --toolsets, --max-turns, --skills (rendered once into the frozen prompt), --checkpoints, --yolo, --accept-hooks (also HERMES_ACCEPT_HOOKS=1), --pass-session-id, --ignore-rules, --ignore-user-config and --safe-mode (both need an explicit --model; safe mode runs the turn in an isolated worker that never reads the profile). Also accepted: --source <label>, --resume <id-or-title>, -c <title>, -c <title> --create-if-missing, --in <dir>, --query-file, --format stream-json, -z ... --usage-file, bare -c (this terminal's breadcrumb session, else the most recent CLI session), --resume latest (most recent CLI session, this workspace first; both resolved by the owner, session.resume latest='cli') and --list-tools / --list-toolsets (print the catalog and exit; no session). chat -q without -Q ends with main's Resume this session with: hermes --resume <id> block.

Refused with exit 2 (_UNSUPPORTED plus the selection checks in validate_options); the error names where each capability lives now:

OptionWhyUse instead
-w / --worktreeWorktree creation is a launcher-side step the authority does not exposehermes --tui -w
--imageNo client-side attachment staging on this pathattach in hermes --tui or Desktop
--run-budgetNo per-launch run budget in the creation contractagent.run_budget_seconds in config.yaml
-v / --verboseDisplay-only; the client has no verbose rendererhermes logs --follow (the refusal also names hermes chat --tui -v, whose canonical Ink path does not yet carry the flag)
--no-restore-cwdThe gateway keeps the session's frozen cwd--in <dir>
--create-if-missing without -c <name>Nothing to create by name-c <name> --create-if-missing

--compact is not a hermes chat option (argparse rejects it); a direct cli.main hand-off refuses it and names display.compact.

Current parity limits — not full classic CLI parity​

Full legacy presentation, slash registry parity, interactive history editing, auto-reconnect, lost-ACK durable client journals, remote login bootstrap, cold-owner resume and native Windows/macOS QA remain outstanding. The client preserves an explicit remote URL's existing authentication mechanism; it does not acquire or mint remote credentials.

tests/hermes_cli/test_gateway_chat_native.py exercises native tmux/PTY classic fresh input, detach, persisted-ID resume and one-shot through the ordinary daemon with a loopback model, isolated HOME/HERMES_HOME and caller cwd. Constructor instrumentation records no client AIAgent, SessionDB or GatewayRunner. The test also denies an invalid approval choice, then reconnects and consents before a real terminal effect on an owned fixture. Stale-control/active-Stop native coverage and native clarify interaction remain separate acceptance work.

For two-checkout compatibility testing, load the narrowly scoped pytest option plugin and specify the disposable gateway peer's checkout (the normal runner's hermetic environment deliberately removes arbitrary environment overrides):

scripts/run_tests.sh -j 1 --file-retries 0 tests/hermes_cli/test_gateway_chat_native.py \
-p tests.hermes_cli.gateway_client_options --client-test-runtime-root=/path/to/runtime-checkout

The receipt explicitly reports caller_cwd_effect_verified. On a cwd-capable runtime the native client submits model/toolsets/cwd and the real terminal writes its actual execution directory into an owned temporary receipt. On an older runtime, explicit --in must reject without that effect. Do not count the latter as cwd parity. Both client and daemon use isolated test homes; this option does not install a service or alter the selected checkout.