From 1fe06115d1ed00ac859e5aa2a6afcde4a2c8bbbe Mon Sep 17 00:00:00 2001 From: teknium1 <127238744+teknium1@users.noreply.github.com> Date: Wed, 29 Jul 2026 08:55:42 -0700 Subject: [PATCH] refactor: extract DEFAULT_CONFIG + OPTIONAL_ENV_VARS to config_defaults.py (pure data move) --- hermes_cli/config.py | 4160 +------------------------------- hermes_cli/config_defaults.py | 4165 +++++++++++++++++++++++++++++++++ 2 files changed, 4166 insertions(+), 4159 deletions(-) create mode 100644 hermes_cli/config_defaults.py diff --git a/hermes_cli/config.py b/hermes_cli/config.py index 44c6376c0aa..c50f7377da6 100644 --- a/hermes_cli/config.py +++ b/hermes_cli/config.py @@ -930,2997 +930,7 @@ def _ensure_hermes_home_managed(home: Path): # Config loading/saving # ============================================================================= -DEFAULT_CONFIG = { - "model": "", - "providers": {}, - "fallback_providers": [], - "credential_pool_strategies": {}, - "toolsets": ["hermes-cli"], - # Global active chat session cap across CLI, TUI/dashboard, and messaging. - # None/0 = unbounded. - "max_concurrent_sessions": None, - # Soft LRU cap on in-memory TUI/desktop/dashboard sessions. When more than - # this many are live, the gateway evicts the least-recently-active DETACHED - # sessions (no live client) so accumulated agents don't pile up under memory - # pressure. Reopening one re-resumes it from disk. 0/null disables. - "max_live_sessions": 16, - "agent": { - "max_turns": 500, - # Inactivity timeout for gateway agent execution (seconds). - # The agent can run indefinitely as long as it's actively calling - # tools or receiving API responses. Only fires when the agent has - # been completely idle for this duration. 0 = unlimited. - "gateway_timeout": 1800, - # Graceful drain timeout for gateway stop/restart (seconds). - # The gateway stops accepting new work, waits for running agents - # to finish, then interrupts any remaining runs after the timeout. - # 0 = no drain, interrupt immediately (the default). - # - # Contract: if you restart the gateway, in-flight work stops. We do - # not hold the restart open for a grace window — a drain timeout - # large enough to "save" a long agent turn would have to outlast an - # unbounded task (some runs take days), which is impossible, and a - # drain timeout shorter than systemd's TimeoutStopSec invites a - # SIGKILL-mid-cleanup race that leaves a stale lock and crash-loops - # the service. 0 sidesteps both: interrupt now, clean up, exit fast. - # Set a positive value in config.yaml only if you explicitly want a - # grace window on /restart (and keep it well under TimeoutStopSec). - "restart_drain_timeout": 0, - # Upper bound (seconds) a submitted prompt waits for the deferred - # agent build (MCP discovery, model metadata, skills scan) before - # failing with a visible error (#63078). The gateway's wait is - # patient — the prompt is delivered the moment the build completes - # and a progress notice is emitted past 30s — so this cap only fires - # on a genuinely hung build. Raise it for deployments with many slow - # or unreachable MCP servers. - "build_wait_timeout": 600, - # Max app-level retry attempts for API errors (connection drops, - # provider timeouts, 5xx, etc.) before the agent surfaces the - # failure. The OpenAI SDK already does its own low-level retries - # (max_retries=2 default) for transient network errors; this is - # the Hermes-level retry loop that wraps the whole call. Lower - # this to 1 if you use fallback providers and want fast failover - # on flaky primaries; raise it if you prefer to tolerate longer - # provider hiccups on a single provider. - "api_max_retries": 3, - "service_tier": "", - # Tool-use enforcement: injects system prompt guidance that tells the - # model to actually call tools instead of describing intended actions. - # Values: "auto" (default — applies to gpt/codex models), true/false - # (force on/off for all models), or a list of model-name substrings - # to match (e.g. ["gpt", "codex", "gemini", "qwen"]). - "tool_use_enforcement": "auto", - # Intent-ack continuation: when the model opens a turn by narrating an - # action it will take ("I'll go check the logs...") but emits no tool - # call, intercept the turn-end, inject a "continue now, execute the - # tools" nudge, and loop instead of ending the turn (capped at 2 nudges - # per turn). This is the corrective sibling of tool_use_enforcement (the - # preventive prompt-side guard). Values: "auto" (default — fires only on - # the codex_responses api_mode, the historical behavior), true (all - # api_modes — fixes the Gemini/Claude "stops after stating intent" case), - # false (never), or a list of model-name substrings to match. - "intent_ack_continuation": "auto", - # Universal "finish the job" guidance — short prompt block applied to - # all models that targets two cross-family failure modes: (1) stopping - # after a stub instead of finishing the artifact, (2) fabricating - # plausible-looking output when a real path is blocked. Costs ~80 - # tokens in the cached system prompt. Set False to disable globally. - "task_completion_guidance": True, - # Universal parallel-tool-call guidance — short prompt block applied to - # all models that tells the model to batch independent tool calls - # (reads, searches, web fetches, read-only commands) into one turn - # instead of one call per turn. The runtime already runs independent - # calls concurrently, so this just steers the model to produce the - # batch — cutting round-trips and the resent-context cost that - # compounds over a long conversation. Costs ~70 tokens in the cached - # system prompt. Set False to disable globally. - "parallel_tool_call_guidance": True, - # Local-environment toolchain probe — surfaces Python/pip/uv/PEP-668 - # state in the system prompt when something non-default is detected - # (e.g. python3 has no pip module, pip→python version mismatch, PEP - # 668 enforcement without uv). Costs zero tokens when the env is - # clean (probe emits nothing). Skipped for remote terminal backends - # (docker/modal/ssh — they have their own probe). Set False to - # disable entirely. - "environment_probe": True, - # Embedder-supplied environment description appended to the system - # prompt's environment-hints block. Lets a host that wraps Hermes - # (sandbox runner, managed platform) explain the runtime environment - # — proxy, credential handling, mount layout — without editing the - # identity slot (SOUL.md). Empty by default. The HERMES_ENVIRONMENT_HINT - # env var overrides this (build-time/container mechanism). - "environment_hint": "", - # Coding posture — on interactive coding surfaces (CLI, TUI, desktop - # app, ACP) in a code workspace, Hermes adds a coding operating brief - # + a live git/workspace snapshot to the system prompt. See - # agent/coding_context.py. - # "auto" (default) — prompt-only posture when the surface is - # interactive AND cwd is a code workspace. - # Toolsets are never touched; messaging platforms - # unaffected. - # "focus" — auto + collapse the toolset to the lean coding - # set (+ enabled MCP servers) + demote non-coding - # skill categories to names-only in the prompt's - # skill index. Explicit opt-in. - # "on" — force the prompt posture everywhere. - # "off" — disable entirely. - "coding_context": "auto", - # Standing operator instructions for the coding posture. A string (or - # list of strings) appended to the coding brief as an extra stable - # system block — pin project-wide workflow rules here instead of editing - # the shipped brief, e.g. "For UI work, don't run tsc/lint until I - # approve. Clean the diff before you commit and push." Cache-safe: - # takes effect next session. Empty by default. - "coding_instructions": "", - # When verify-on-stop finds edited code without fresh verification - # evidence, append guidance for creative UI work (avoid broad - # tsc/lint/test before visual approval) and clean-diff expectations. - # Set false to keep the evidence nudge terse. - "verify_guidance": True, - # Upper bound on consecutive `pre_verify` "continue" nudges in a single - # turn, so a user/plugin hook can never trap the loop. - "max_verify_nudges": 3, - # Verification closure: after the agent edits files in a code workspace, - # do not accept a final answer until fresh verification evidence exists - # or the agent explains why it cannot run checks. The loop is bounded - # and uses the passive verification ledger. Default is "auto" — - # surface-aware: on for interactive coding surfaces (CLI, TUI, desktop) - # and programmatic callers, off for conversational messaging surfaces - # (Telegram, Discord, etc.) where the verification narrative would reach - # a human as chat noise. Doc/markdown/skill-only edits never fire it. - # Set true to force on everywhere, or false to disable. - "verify_on_stop": "auto", - # Staged inactivity warning: send a warning to the user at this - # threshold before escalating to a full timeout. The warning fires - # once per run and does not interrupt the agent. 0 = disable warning. - "gateway_timeout_warning": 900, - # Maximum time (seconds) the gateway will block an agent waiting for - # a clarify-tool response from the user. Hit this and the agent - # unblocks with "[user did not respond within Xm]" so it can adapt - # rather than pinning the running-agent guard forever. CLI clarify - # blocks indefinitely (input() is synchronous) and ignores this. - # Default 3600 (1h): real users step away (meetings, AFK) and the - # old 600s default evicted the entry mid-think, so a later button - # tap landed on a dead entry (#32762). Tradeoff: a higher value - # holds the gateway's running-agent guard longer for a genuinely - # abandoned prompt — lower it if a single session must free up the - # guard sooner. - "clarify_timeout": 3600, - # Periodic "still working" notification interval (seconds). - # Sends a status message every N seconds so the user knows the - # agent hasn't died during long tasks. 0 = disable notifications. - # Lower values mean faster feedback on slow tasks but more chat - # noise; 180s is a compromise that catches spinning weak-model runs - # (60+ tool iterations with tiny output) before users assume the - # bot is dead and /restart. - "gateway_notify_interval": 180, - # Freshness window for the gateway auto-continue note (seconds). - # After a gateway crash/restart/SIGTERM mid-run, the next user - # message gets a "[System note: your previous turn was - # interrupted — process the unfinished tool result(s) first]" - # prepended so the model picks up where it left off. That's the - # right behaviour while the interruption is fresh, but stale - # markers (transcript last touched hours or days ago) can revive - # an unrelated old task when the user's next message starts new - # work. This window is the max age of the last persisted - # transcript row for which we still inject the continue note. - # Default 3600s comfortably covers a long turn (gateway_timeout - # default is 1800s) plus runtime slack. Set to 0 to disable the - # gate and restore pre-fix behaviour (always inject). - "gateway_auto_continue_freshness": 3600, - # Max seconds the gateway waits for boot auto-resume turns to finish - # before it releases the startup-restore inbound gate. While startup - # restore is in progress the gateway QUEUES every inbound message - # instead of replying, so no channel gets an answer until this gate - # opens. Without a bound, one pathologically long resumed turn holds - # the gate shut and every channel's inbound piles up unanswered for as - # long as that turn runs. On timeout the gate releases and the slow - # resume turn keeps running in the background; duplicate-agent - # protection is unaffected because the resume slot is claimed - # synchronously before the gate runs. Set to 0 to disable the bound - # (historical "wait forever" behaviour). - "gateway_startup_restore_drain_timeout": 30, - # Stale-stream ceiling for local providers (Ollama, oMLX, llama-cpp) in - # seconds. When the base stale timeout is at its default (180s) and a - # local endpoint is detected, this finite ceiling replaces the former - # infinite disable so a wedged local server eventually trips the - # detector instead of hanging forever. The env var - # ``HERMES_LOCAL_STREAM_STALE_TIMEOUT`` overrides for escape-hatch use. - "local_stream_stale_timeout": 900, - # How user-attached images are presented to the main model on each turn. - # "auto" — attach natively when the active model reports - # supports_vision=True AND the user hasn't explicitly - # configured auxiliary.vision.provider. Otherwise fall - # back to text (vision_analyze pre-analysis). - # "native" — always attach natively; non-vision models will either - # error at the provider or get a last-chance text fallback - # (see run_agent._prepare_messages_for_api). - # "text" — always pre-analyze with vision_analyze and prepend the - # description as text; the main model never sees pixels. - # Affects gateway platforms, the TUI, and CLI /attach. vision_analyze - # remains available as a tool regardless of this setting — the routing - # only controls how inbound user images are presented. - "image_input_mode": "auto", - "disabled_toolsets": [], - - # Per-model reasoning effort overrides (spelling-tolerant). - # Dict mapping model names (any reasonable spelling) to effort levels. - # Takes precedence over agent.reasoning_effort when the current model - # matches a key in this dict. - # Edit directly in config.yaml (no CLI support due to dots in keys). - "reasoning_overrides": {}, - }, - - "terminal": { - "backend": "local", - "modal_mode": "auto", - "cwd": ".", # Use current directory - "timeout": 180, - # Bounded grace period (seconds) between SIGTERM and an escalated - # SIGKILL when terminating a host process tree (browser daemons, etc.). - # A daemon that stalls in its SIGTERM handler is force-killed after this - # window so it can't leak indefinitely. 0 disables escalation (SIGTERM - # only — the historical behavior). Floored internally at 0. - "daemon_term_grace_seconds": 2.0, - # Environment variables to pass through to sandboxed execution - # (terminal and execute_code). Skill-declared required_environment_variables - # are passed through automatically; this list is for non-skill use cases. - "env_passthrough": [], - # HOME handling for host tool subprocesses: - # auto — host keeps the real OS-user HOME; containers use - # HERMES_HOME/home for persistent state (default) - # real — force the real OS-user HOME - # profile — force HERMES_HOME/home when it exists (old strict - # per-profile CLI config isolation) - "home_mode": "auto", - # Extra files to source in the login shell when building the - # per-session environment snapshot. Use this when tools like nvm, - # pyenv, asdf, or custom PATH entries are registered by files that - # a bash login shell would skip — most commonly ``~/.bashrc`` - # (bash doesn't source bashrc in non-interactive login mode) or - # zsh-specific files like ``~/.zshrc`` / ``~/.zprofile``. - # Paths support ``~`` / ``${VAR}``. Missing files are silently - # skipped. When empty, Hermes auto-sources ``~/.profile``, - # ``~/.bash_profile``, and ``~/.bashrc`` (in that order) if the - # snapshot shell is bash (this is the ``auto_source_bashrc`` - # behaviour — disable with that key if you want strict login-only - # semantics). - "shell_init_files": [], - # When true (default), Hermes sources the user's shell rc files - # (``~/.profile``, ``~/.bash_profile``, ``~/.bashrc``) in the - # login shell used to build the environment snapshot. This - # captures PATH additions, shell functions, and aliases — which a - # plain ``bash -l -c`` would otherwise miss because bash skips - # bashrc in non-interactive login mode, and because a default - # Debian/Ubuntu ``~/.bashrc`` short-circuits on non-interactive - # sources. ``~/.profile`` and ``~/.bash_profile`` are tried first - # because ``n`` / ``nvm`` / ``asdf`` installers typically write - # their PATH exports there without an interactivity guard. Turn - # this off if your rc files misbehave when sourced - # non-interactively (e.g. one that hard-exits on TTY checks). - "auto_source_bashrc": True, - "docker_image": "nikolaik/python-nodejs:python3.11-nodejs20", - "docker_forward_env": [], - # Explicit environment variables to set inside Docker containers. - # Unlike docker_forward_env (which reads values from the host process), - # docker_env lets you specify exact key-value pairs — useful when Hermes - # runs as a systemd service without access to the user's shell environment. - # Example: {"SSH_AUTH_SOCK": "/run/user/1000/ssh-agent.sock"} - "docker_env": {}, - "singularity_image": "docker://nikolaik/python-nodejs:python3.11-nodejs20", - "modal_image": "nikolaik/python-nodejs:python3.11-nodejs20", - "daytona_image": "nikolaik/python-nodejs:python3.11-nodejs20", - # Container resource limits (docker, singularity, modal, daytona — ignored for local/ssh) - "container_cpu": 1, - "container_memory": 5120, # MB (default 5GB) - "container_disk": 51200, # MB (default 50GB) - "container_persistent": True, # Persist filesystem across sessions - # Docker volume mounts — share host directories with the container. - # Each entry is "host_path:container_path" (standard Docker -v syntax). - # Example: - # ["/home/user/projects:/workspace/projects", - # "/home/user/.hermes/cache/documents:/output"] - # For gateway MEDIA delivery, write inside Docker to /output/... and emit - # the host-visible path in MEDIA:, not the container path. - "docker_volumes": [], - # Explicit opt-in: mount the host cwd into /workspace for Docker sessions. - # Default off because passing host directories into a sandbox weakens isolation. - "docker_mount_cwd_to_workspace": False, - # Opt-in egress lockdown for Docker terminal sessions. When false, - # Docker runs with --network=none so commands cannot reach the network. - "docker_network": True, - "docker_extra_args": [], # Extra flags passed verbatim to docker run - # Explicit opt-in: run the Docker container as the host user's uid:gid - # (via `--user`). When enabled, files written into bind-mounted dirs - # (docker_volumes, the persistent workspace, or the auto-mounted cwd) - # are owned by your host user instead of root, which avoids needing - # `sudo chown` after container runs. Default off to preserve behavior - # for images whose entrypoints expect to start as root (e.g. the - # bundled Hermes image, which drops to the `hermes` user via - # s6-setuidgid inside each supervised service). - # When on, SETUID/SETGID caps are omitted from the container since - # no privilege drop is needed. - "docker_run_as_host_user": False, - # Persistent shell — keep a long-lived bash shell across execute() calls - # so cwd/env vars/shell variables survive between commands. - # Enabled by default for non-local backends (SSH); local is always opt-in - # via TERMINAL_LOCAL_PERSISTENT env var. - "persistent_shell": True, - }, - - "web": { - "backend": "", # shared fallback — applies to both search and extract - "search_backend": "", # per-capability override for web_search (e.g. "searxng") - "extract_backend": "", # per-capability override for web_extract (e.g. "native") - "extract_char_limit": 15000, # per-page char budget for web_extract; larger pages truncate + store full text in cache/web - }, - - "browser": { - "inactivity_timeout": 120, - "command_timeout": 30, # Timeout for browser commands in seconds (screenshot, navigate, etc.) - "record_sessions": False, # Auto-record browser sessions as WebM videos - "headed": False, # Local mode: launch Chromium with a visible window (also skips per-turn cleanup so the window persists between turns; idle reaper still applies) - "allow_private_urls": False, # Allow navigating to private/internal IPs (localhost, 192.168.x.x, etc.) - # Browser engine for local mode. Passed as ``--engine `` to - # agent-browser v0.25.3+. - # "auto" — use Chrome (default, don't pass --engine at all) - # "lightpanda" — use Lightpanda (1.3-5.8x faster navigation, no screenshots) - # "chrome" — explicitly request Chrome - # Also settable via AGENT_BROWSER_ENGINE env var. - "engine": "auto", - "auto_local_for_private_urls": True, # When a cloud provider is set, auto-spawn local Chromium for LAN/localhost URLs instead of sending them to the cloud - "cdp_url": "", # Optional persistent CDP endpoint for attaching to an existing Chromium/Chrome - "allow_unsafe_evaluate": False, # Legacy override: when true, browser_console(expression=...) bypasses the restrict_evaluate denylist entirely - "restrict_evaluate": False, # Opt-in denylist blocking sensitive JS primitives (cookies/storage/clipboard/network/form values) in browser_console(expression=...) - # CDP supervisor — dialog + frame detection via a persistent WebSocket. - # Active only when a CDP-capable backend is attached (Browserbase or - # local Chrome via /browser connect). See - # website/docs/developer-guide/browser-supervisor.md. - "dialog_policy": "must_respond", # must_respond | auto_dismiss | auto_accept - "dialog_timeout_s": 300, # Safety auto-dismiss after N seconds under must_respond - "camofox": { - # When true, Hermes sends a stable profile-scoped userId to Camofox - # so the server maps it to a persistent Firefox profile automatically. - # When false (default), each session gets a random userId (ephemeral). - "managed_persistence": False, - # Optional externally managed Camofox identity. Useful when another - # app owns the visible browser and Hermes should operate in it. - "user_id": "", - "session_key": "", - # Rehydrate tab_id from Camofox before creating a new tab. - "adopt_existing_tab": False, - # Docker Camofox opens page URLs from inside the container. Enable - # this to rewrite loopback page URLs (localhost/127.0.0.1/::1) to a - # host alias while leaving CAMOFOX_URL itself unchanged. - "rewrite_loopback_urls": False, - "loopback_host_alias": "host.docker.internal", - }, - }, - - # Filesystem checkpoints — automatic snapshots before destructive file ops. - # When enabled, the agent takes a snapshot of the working directory once - # per conversation turn (on first write_file/patch call). Use /rollback - # to restore. - # - # Defaults changed in v2 (single shared shadow store, real pruning): - # - enabled: True -> False (opt-in; most users never use /rollback) - # - max_snapshots: 50 -> 20 (now actually enforced via ref rewrite) - # - auto_prune: False -> True (orphans/stale pruned automatically) - # Opt in via ``hermes chat --checkpoints`` or set enabled=True here. - "checkpoints": { - "enabled": False, - # Max checkpoints to keep per working directory. Pre-v2 this only - # limited the `/rollback` listing; v2 actually rewrites the ref and - # garbage-collects older commits. - "max_snapshots": 20, - # Hard ceiling on total ``~/.hermes/checkpoints/`` size (MB). When - # exceeded, the oldest checkpoint per project is dropped in a - # round-robin pass until total size falls under the cap. - # 0 disables the size cap. - "max_total_size_mb": 500, - # Skip any single file larger than this when staging a checkpoint. - # Prevents accidental snapshotting of datasets, model weights, and - # other large generated assets. 0 disables the filter. - "max_file_size_mb": 10, - # Auto-maintenance: hermes sweeps the checkpoint base at startup - # (at most once per ``min_interval_hours``) and: - # * deletes project entries whose last_touch is older than - # ``retention_days`` - # * GCs the single shared store to reclaim unreachable objects - # * enforces ``max_total_size_mb`` across remaining projects - # * deletes ``legacy-*`` archives older than ``retention_days`` - # - # NOTE: this automatic sweep never deletes "orphan" entries (workdir - # no longer found on disk). A missing workdir at startup is - # ambiguous — it can mean the project was deleted, or that an - # external volume / network share / VPN is simply not mounted yet — - # and this sweep runs unattended, so it must never guess. Orphan - # cleanup is only available via the explicit - # ``hermes checkpoints prune`` command (add ``--keep-orphans`` to - # skip it), where a human is looking at the output. - "auto_prune": True, - "retention_days": 7, - "min_interval_hours": 24, - }, - - # Hard cap (chars) for a single automatic context file such as SOUL.md, - # AGENTS.md, CLAUDE.md, .hermes.md, or .cursorrules before Hermes applies - # head/tail truncation. ``null`` (the default) lets the cap scale with the - # model's context window (floor 20K, ceiling 500K) so large-context models - # rarely truncate a project doc. Set a positive integer to pin a fixed cap - # and override the dynamic behavior. Separate from read_file tool limits. - "context_file_max_chars": None, - - # Maximum characters returned by a single read_file call. Reads that - # exceed this are rejected with guidance to use offset+limit. - # 100K chars ≈ 25–35K tokens across typical tokenisers. - "file_read_max_chars": 100_000, - - # Seconds to wait at agent-build time for in-flight MCP server discovery - # to finish before the agent snapshots its tool list. MCP discovery runs - # in a background thread so a slow/dead server can't freeze startup; this - # bounds how long the first agent build blocks on it. The wait returns - # the INSTANT discovery completes, so users with no MCP servers (the common - # case) or fast servers pay ~0s regardless of this value — the bound is - # only reached when a server is genuinely still connecting. The old 0.75s - # default was a touch short for HTTP/OAuth servers on a cold connect; a - # modest bump lets more of them land in the FIRST turn's snapshot. This is - # only a turn-1 latency/UX knob: a server that misses this window is still - # picked up automatically on the next turn by the between-turns refresh - # (see agent/turn_context.py), so correctness never depends on it. Keep it - # small so a slow/dead server adds little to first-response latency. - "mcp_discovery_timeout": 1.5, - - # MCP runtime behavior (distinct from the per-server definitions in - # mcp_servers: and from the auxiliary.mcp side-LLM task settings). - "mcp": { - # Auto-reload MCP connections when config.yaml's mcp_servers section - # changes at runtime (CLI file watcher, default on). - # Set to false to stop the automatic reload: every automatic reload - # rebuilds the agent tool surface and INVALIDATES the provider - # prompt cache (the next message re-sends the full input prefix), - # which is expensive on long-context / high-reasoning models. - # When disabled, the watcher still detects the change and prints - # guidance to apply it deliberately via /reload-mcp. - "auto_reload_on_config_change": True, - }, - - # Tool-output truncation thresholds. When terminal output or a - # single read_file page exceeds these limits, Hermes truncates the - # payload sent to the model (keeping head + tail for terminal, - # enforcing pagination for read_file). Tuning these trades context - # footprint against how much raw output the model can see in one - # shot. Ported from anomalyco/opencode PR #23770. - # - # - max_bytes: terminal_tool output cap, in chars - # (default 50_000 ≈ 12-15K tokens). - # - max_lines: read_file pagination cap — the maximum `limit` - # a single read_file call can request before - # being clamped (default 2000). - # - max_line_length: per-line cap applied when read_file emits a - # line-numbered view (default 2000 chars). - "tool_output": { - "max_bytes": 50_000, - "max_lines": 2000, - "max_line_length": 2000, - }, - - # Tool loop guardrails nudge models when they repeat failed or - # non-progressing tool calls. Soft warnings are always-on by default; - # hard stops are opt-in so interactive CLI/TUI sessions keep flowing. - "tool_loop_guardrails": { - "warnings_enabled": True, - "hard_stop_enabled": False, - "warn_after": { - "exact_failure": 2, - "same_tool_failure": 3, - "idempotent_no_progress": 2, - }, - "hard_stop_after": { - "exact_failure": 5, - "same_tool_failure": 8, - "idempotent_no_progress": 5, - }, - # Per-turn runaway-loop caps (inspired by Claude Code v2.1.212, - # Week 29, July 2026). Hard ceilings on how many times a runaway-prone - # tool may be called within a SINGLE agent loop (turn); the counters - # reset at the start of every turn, so a legitimate multi-turn session - # is never starved. They are always-on and fire regardless of the - # warn/hard-stop thresholds above. A single turn issuing dozens of web - # searches or spawning dozens of subagents is already pathological, so - # the defaults are low. Set either to 0 to disable that cap (unlimited). - "loop_caps": { - "max_web_searches": 50, # max web_search calls per turn (0 = unlimited) - "max_subagents": 50, # max subagents spawned per turn (0 = unlimited) - }, - }, - - "compression": { - "enabled": True, - "progress_notices": False, # opt-in (#52995): when True, routine compression - # progress statuses (compacting/preflight/pre-API/ - # idle/retry) are delivered to chat gateway - # platforms instead of being suppressed by the - # gateway noise filter. Default False keeps - # routine compression silent-by-design on chat - # surfaces (server-side logging only). Failure - # notices and manual /compress feedback are - # always visible regardless of this setting. - "threshold": 0.50, # compress when context usage exceeds this ratio. - # Models with context windows below 512K are - # floored at 0.75 (raise-only) so compaction - # doesn't fire with half the window still free; - # set this above 0.75 to override the floor. - "threshold_tokens": None, # absolute token cap — when set, compression - # triggers at the lower of the ratio-based - # threshold and this token count. Clamped to - # the model's context length at apply-time. - "target_ratio": 0.20, # fraction of threshold to preserve as recent tail - "protect_last_n": 20, # minimum recent messages to keep uncompressed - "min_tail_user_messages": 1, # REAL (actionable) user messages guaranteed to - # survive in the uncompressed tail. 1 = existing - # single last-user anchor (default, behavior- - # preserving); raise to e.g. 3 to keep the last - # 3 real user turns verbatim when bulky tool - # outputs fill the tail token budget. - "max_attempts": 3, # compression retry rounds before a turn gives up - # with "max compression attempts reached". Raise - # (e.g. 6) for tool-schema-heavy sessions where 3 - # rounds cannot clear the request estimate. - # Validated >= 1, hard-capped at 10. - "proactive_prune_tokens": 0, # opt-in trigger (tokens) for the deterministic, - # no-LLM tool-result prune, run independently of - # `threshold` above. On large-window models - # `threshold` (≈50% of the window) rarely fires, - # so old tool output otherwise rides in history - # and is re-sent every turn; a low value like - # 48000 reclaims it early. 0 = off. Recent tail - # protected by `protect_last_n`. Built-in - # compressor only (other engines inherit a no-op). - # NOTE: each committed prune rewrites already-sent - # history, breaking the provider prompt-cache - # prefix — the min_reclaim gate below keeps those - # breaks episodic rather than per-turn. - "proactive_prune_min_result_chars": 8000, # the prune's summarize pass only - # touches tool results larger than this (chars); - # clamped to >= 200 so a generated summary can't - # itself be re-summarized. - "proactive_prune_min_reclaim_tokens": 4096, # a proactive prune only commits - # when it reclaims at least this many tokens - # (measured on the pruned output). Keeps - # prompt-cache invalidation amortized: one big - # episodic break instead of a tiny break every - # tool iteration. 0 = commit any non-zero prune. - "hygiene_hard_message_limit": 5000, # gateway session-hygiene force-compress threshold by message count - "hygiene_timeout_seconds": 30, # max seconds gateway waits for pre-agent hygiene compression - # WITHOUT forward progress. The summary call streams, so - # this is an inactivity budget: a slow model still - # producing tokens keeps extending the wait; only a - # silent/hung call is cut off. - "hygiene_total_ceiling_seconds": 600, # absolute cap on the hygiene compression wait even - # while tokens are still moving — bounds a degenerate - # trickle stream. Clamped to >= hygiene_timeout_seconds. - "hygiene_failure_cooldown_seconds": 300, # skip repeated failed hygiene attempts for this session - "protect_first_n": 3, # non-system head messages always preserved - # verbatim, in ADDITION to the system prompt - # (which is always implicitly protected). Set to - # 0 for long-running rolling-compaction sessions - # where you want nothing pinned except the - # system prompt + rolling summary + recent tail. - "abort_on_summary_failure": False, # When True, auto-compression that fails - # to generate a summary (aux LLM errored / returned - # non-JSON / timed out) aborts entirely instead of - # dropping the middle window with a static - # "summary unavailable" placeholder. Messages are - # preserved unchanged and the session "freezes" at - # its current size until the user runs /compress - # (which bypasses the failure cooldown) or /new. - # Default False matches historical behavior; set to - # True if you'd rather pause than silently lose - # context turns when your aux model is flaky. - "codex_gpt55_autoraise": True, # Historical key name kept for compatibility. - # When True, gpt-5.4 / gpt-5.5 / gpt-5.6 on the - # ChatGPT Codex OAuth route raise their compaction - # trigger to 85% (vs the global `threshold` above). - # Codex hard-caps these families at a 272K window, so - # the default 50% would compact at ~136K and waste half - # the usable context. Set to False to opt back down to - # the global threshold (e.g. 0.50) for those Codex - # sessions. Only this exact route is affected — - # gpt-5.4 / 5.5 / 5.6 on OpenAI's direct API, - # OpenRouter, and Copilot keep the global threshold - # regardless. - "codex_gpt55_autoraise_notice": True, # Display the one-time Codex gpt-5.4/5.5/5.6 - # autoraise banner. Set False to keep the - # 85% threshold autoraise but suppress the - # user-facing notice in CLI/gateway output. - "codex_app_server_auto": "native", # Codex app-server (codex CLI runtime) thread - # compaction mode. The codex agent owns the real - # thread context, so Hermes' summarizer cannot - # shrink it (#36801). native = codex decides when - # to compact its own thread (default); hermes = - # Hermes' compression threshold triggers - # thread/compact/start; off = never auto-trigger - # (codex may still compact natively). - "in_place": True, # When True, compaction rewrites the message - # list and rebuilds the system prompt WITHOUT - # rotating the session id — the conversation - # keeps one durable id for its whole life - # (no parent_session_id chain, no `name #N` - # renumbering). Eliminates the session-rotation - # bug cluster (#33618 /goal loss, #14238 lost - # response, #33907 orphans, #45117 search gaps, - # #42228 null cwd) — see #38763. Non-destructive: - # the live context is compacted (lossy for what - # the model reloads), but the pre-compaction - # turns are soft-archived under the same id - # (active=0, compacted=1) — still searchable via - # session_search and recoverable, not deleted. - # Default True since 2107b86024; set False to - # restore the legacy rotating-compaction path. - "model_thresholds": {}, # Per-model threshold overrides. Keys are - # substring-matched against the model name - # (longest match wins); values replace the - # global `threshold` for that model, e.g. - # model_thresholds: - # "glm-5.2": 0.40 - # "claude-sonnet": 0.35 - # The small-context floor (0.75 for <512K - # models) still applies on top of overrides - # (raise-only: an override above the floor - # wins; one below it is raised to the floor). - "idle_compact_after_seconds": 0, # Opt-in idle compaction (0 = disabled). - # When > 0, a session that resumes after at - # least this many seconds of inactivity - # compacts its accumulated history up front, - # before the first reply — so a long-lived - # thread resumed hours later doesn't re-read - # its full stale context on every turn. - # Time-based; complements (does not replace) - # the size-based `threshold` above. Skipped - # when the context is already at/below the - # post-compression target (threshold × - # target_ratio) and it honors the same - # failure-cooldown / anti-thrash / per-session - # lock guards as every automatic compaction. - # Example: 1800 = compact after 30 min idle. - }, - - # Anthropic prompt caching (Claude via OpenRouter or native Anthropic API). - # cache_ttl must be "5m" or "1h" (Anthropic-supported tiers); other values are ignored. - "prompt_caching": { - "cache_ttl": "5m", - }, - - # OpenRouter-specific settings. - # response_cache: enable OpenRouter response caching (X-OpenRouter-Cache header). - # When enabled, identical requests return cached responses for free (zero billing). - # This is separate from Anthropic prompt caching and works alongside it. - # See: https://openrouter.ai/docs/guides/features/response-caching - # response_cache_ttl: how long cached responses remain valid, in seconds (1-86400). - # Default 300 (5 minutes). Only used when response_cache is enabled. - # min_coding_score: knob for the openrouter/pareto-code router (0.0-1.0). - # Only applied when model.model is "openrouter/pareto-code". Higher - # values route to stronger (more expensive) coders; lower values open - # up cheaper, faster options. Default 0.65 lands on the mid-tier - # coder on the current Pareto frontier. Empty string = let OpenRouter - # pick the strongest available coder (router's documented default - # when the plugins block is omitted). - # See: https://openrouter.ai/docs/guides/routing/routers/pareto-router - "openrouter": { - "response_cache": True, - "response_cache_ttl": 300, - "min_coding_score": 0.65, - }, - - # AWS Bedrock provider configuration. - # Only used when model.provider is "bedrock". - "bedrock": { - "region": "", # AWS region for Bedrock API calls (empty = AWS_REGION env var → us-east-1) - "discovery": { - "enabled": True, # Auto-discover models via ListFoundationModels - "provider_filter": [], # Only show models from these providers (e.g. ["anthropic", "amazon"]) - "refresh_interval": 3600, # Cache discovery results for this many seconds - }, - "guardrail": { - # Amazon Bedrock Guardrails — content filtering and safety policies. - # Create a guardrail in the Bedrock console, then set the ID and version here. - # See: https://docs.aws.amazon.com/bedrock/latest/userguide/guardrails.html - "guardrail_identifier": "", # e.g. "abc123def456" - "guardrail_version": "", # e.g. "1" or "DRAFT" - "stream_processing_mode": "async", # "sync" or "async" - "trace": "disabled", # "enabled", "disabled", or "enabled_full" - }, - }, - - # Auxiliary model config — provider:model for each side task. - # Format: provider is the provider name, model is the model slug. - # "auto" for provider = auto-detect best available provider. - # Empty model = use provider's default auxiliary model. - # All tasks fall back to openrouter:google/gemini-3-flash-preview if - # the configured provider is unavailable. - # - # extra_body: forwarded verbatim as request body fields on every aux call - # for that task. Use this to set provider-specific knobs (independent of - # main-agent settings). On OpenRouter you can set provider routing prefs - # and the Pareto Code coding-score floor here. Example: - # - # auxiliary: - # compression: - # provider: openrouter - # model: openrouter/pareto-code - # extra_body: - # provider: # OpenRouter provider routing - # order: [anthropic, google] - # sort: throughput # or price | latency - # plugins: # OpenRouter Pareto Code router - # - id: pareto-router - # min_coding_score: 0.5 - # - # Each aux task is independent — main-agent provider_routing and - # openrouter.min_coding_score do NOT propagate to aux calls by design. - "auxiliary": { - # Same-provider retries for a transient transport blip (connection - # reset / timeout / 5xx / 408) on ANY auxiliary call before falling - # back. Default 2 (→ 3 total attempts), clamped [0,6]. Matters most for - # pinned calls like MoA reference advisors, where provider fallback is - # not a meaningful recovery, so an unretried blip silently loses the - # call. - "transient_retries": 2, - # Endpoints that reject NON-streaming chat requests outright (e.g. - # Tencent Copilot returns HTTP 400 "Non-stream chat request is - # currently not supported"). Auxiliary calls to a matching endpoint - # are sent with stream=True and aggregated client-side. Entries are - # case-insensitive substrings matched against the endpoint URL; - # copilot.tencent.com is always treated as stream-only. - "stream_only_base_urls": [], - "vision": { - "provider": "auto", # auto | openrouter | nous | codex | custom - "model": "", # e.g. "google/gemini-2.5-flash", "gpt-4o" - "base_url": "", # direct OpenAI-compatible endpoint (takes precedence over provider) - "api_key": "", # API key for base_url (falls back to OPENAI_API_KEY) - "timeout": 120, # seconds — LLM API call timeout; vision payloads need generous timeout - "extra_body": {}, # OpenAI-compatible provider-specific request fields - "reasoning_effort": "", # per-task thinking level: none|minimal|low|medium|high|xhigh|max|ultra (empty = provider default) - "download_timeout": 30, # seconds — image HTTP download timeout; increase for slow connections - }, - "web_extract": { - "provider": "auto", - "model": "", - "base_url": "", - "api_key": "", - "timeout": 360, # seconds (6min) — per-attempt LLM summarization timeout; increase for slow local models - "extra_body": {}, - "reasoning_effort": "", # per-task thinking level: none|minimal|low|medium|high|xhigh|max|ultra (empty = provider default) - }, - "compression": { - "provider": "auto", - "model": "", - "base_url": "", - "api_key": "", - "timeout": 120, # seconds — compression summarises large contexts; increase for local models - "extra_body": {}, - "reasoning_effort": "", # per-task thinking level: none|minimal|low|medium|high|xhigh|max|ultra (empty = provider default) - }, - # Note: session_search no longer uses an auxiliary LLM (PR #27590 — - # single-shape tool returns DB content directly). The old - # ``auxiliary.session_search.*`` block was removed here. Existing - # values in user config.yaml files are harmless leftovers and ignored. - "skills_hub": { - "provider": "auto", - "model": "", - "base_url": "", - "api_key": "", - "timeout": 30, - "extra_body": {}, - "reasoning_effort": "", # per-task thinking level: none|minimal|low|medium|high|xhigh|max|ultra (empty = provider default) - }, - "approval": { - "provider": "auto", - "model": "", # fast/cheap model recommended (e.g. gemini-flash, haiku) - "base_url": "", - "api_key": "", - "timeout": 30, - "extra_body": {}, - "reasoning_effort": "", # per-task thinking level: none|minimal|low|medium|high|xhigh|max|ultra (empty = provider default) - }, - "mcp": { - "provider": "auto", - "model": "", - "base_url": "", - "api_key": "", - "timeout": 30, - "extra_body": {}, - "reasoning_effort": "", # per-task thinking level: none|minimal|low|medium|high|xhigh|max|ultra (empty = provider default) - }, - "title_generation": { - "enabled": True, - "provider": "auto", - "model": "", - "base_url": "", - "api_key": "", - "timeout": 30, - "extra_body": {}, - "reasoning_effort": "", # per-task thinking level: none|minimal|low|medium|high|xhigh|max|ultra (empty = provider default) - "language": "", - }, - "memory_query_rewrite": { - "provider": "auto", - "model": "", - "base_url": "", - "api_key": "", - "timeout": 8, - "extra_body": {}, - }, - "tts_audio_tags": { - "provider": "auto", - "model": "", - "base_url": "", - "api_key": "", - "timeout": 30, - "extra_body": {}, - "reasoning_effort": "", # per-task thinking level: none|minimal|low|medium|high|xhigh|max|ultra (empty = provider default) - }, - # Triage specifier — flesh out a rough one-liner in the Kanban - # Triage column into a concrete spec, then promote it to ``todo``. - # Invoked by ``hermes kanban specify`` (single id or --all). Set a - # cheap, capable model here (gemini-flash works well); the main - # model is overkill for short spec expansion. - "triage_specifier": { - "provider": "auto", - "model": "", - "base_url": "", - "api_key": "", - "timeout": 120, - "extra_body": {}, - "reasoning_effort": "", # per-task thinking level: none|minimal|low|medium|high|xhigh|max|ultra (empty = provider default) - }, - # Kanban decomposer — decomposes a triage task into a graph of - # child tasks routed to specialist profiles by description. - # Invoked by ``hermes kanban decompose`` and the kanban - # auto-decompose dispatcher tick. Returns a JSON task graph; - # uses more tokens than the specifier so allow more headroom. - "kanban_decomposer": { - "provider": "auto", - "model": "", - "base_url": "", - "api_key": "", - "timeout": 180, - "extra_body": {}, - "reasoning_effort": "", # per-task thinking level: none|minimal|low|medium|high|xhigh|max|ultra (empty = provider default) - }, - # Profile describer — auto-generates a 1-2 sentence description - # of what a profile is good at. Invoked by - # ``hermes profile describe --auto`` and the dashboard's - # auto-generate button. Short, cheap call. - "profile_describer": { - "provider": "auto", - "model": "", - "base_url": "", - "api_key": "", - "timeout": 60, - "extra_body": {}, - "reasoning_effort": "", # per-task thinking level: none|minimal|low|medium|high|xhigh|max|ultra (empty = provider default) - }, - # Goal judge — evaluates whether a /goal run's latest response - # satisfies the goal/contract, and drafts goal contracts. Short - # structured-JSON calls; a fast cheap model is fine. - "goal_judge": { - "provider": "auto", - "model": "", - "base_url": "", - "api_key": "", - "timeout": 60, - "extra_body": {}, - "reasoning_effort": "", # per-task thinking level: none|minimal|low|medium|high|xhigh|max|ultra (empty = provider default) - }, - # Curator — skill-usage review fork. Timeout is generous because the - # review pass can take several minutes on reasoning models (umbrella - # building over hundreds of candidate skills). "auto" = use main chat - # model; override via `hermes model` → auxiliary → Curator to route - # to a cheaper aux model (e.g. openrouter google/gemini-3-flash-preview). - "curator": { - "provider": "auto", - "model": "", - "base_url": "", - "api_key": "", - "timeout": 600, - "extra_body": {}, - "reasoning_effort": "", # per-task thinking level: none|minimal|low|medium|high|xhigh|max|ultra (empty = provider default) - }, - # Monitor — urgency/importance classifier used by the important-mail - # monitor catalog automation (cron/scripts/classify_items.py). Scores - # candidate items 0-10 against the user's criteria so only above- - # threshold items get delivered. "auto" = main chat model; override to - # a cheap fast model (e.g. openrouter google/gemini-3-flash-preview, - # haiku) since per-item scoring is high-volume and a small model is fine. - "monitor": { - "provider": "auto", - "model": "", - "base_url": "", - "api_key": "", - "timeout": 60, - "extra_body": {}, - "reasoning_effort": "", # per-task thinking level: none|minimal|low|medium|high|xhigh|max|ultra (empty = provider default) - }, - # Background review — the post-turn self-improvement fork that decides - # whether to save a memory / patch a skill. "auto" (default) = run on - # the main chat model, replaying the full conversation, which is already - # warm in the prompt cache (cheap cache reads) — unchanged, optimal. - # Set provider/model to a cheaper model (e.g. openrouter - # google/gemini-3-flash-preview) to run the review there for ~3-5x lower - # cost. A different model can't reuse the main prompt cache anyway, so - # the fork automatically replays a compact digest instead of the full - # transcript when routed (minimises the cold-write). Same model = full - # replay; different model = digest. Quality holds (memory capture - # identical, skill near-identical in benchmarks). - "background_review": { - "provider": "auto", - "model": "", - "base_url": "", - "api_key": "", - "timeout": 120, - "extra_body": {}, - "reasoning_effort": "", # per-task thinking level: none|minimal|low|medium|high|xhigh|max|ultra (empty = provider default) - }, - "moa_reference": { - "provider": "auto", - "model": "", - "base_url": "", - "api_key": "", - "timeout": 900, - "extra_body": {}, - # NOTE: no reasoning_effort here by design — MoA reasoning depth is - # configured PER SLOT in the MoA preset (moa.presets.. - # reference_models[].reasoning_effort / aggregator.reasoning_effort), - # not at the auxiliary-task level. - }, - "moa_aggregator": { - "provider": "auto", - "model": "", - "base_url": "", - "api_key": "", - "timeout": 900, - "extra_body": {}, - # NOTE: no reasoning_effort here by design — see moa_reference above. - }, - }, - - "display": { - "compact": False, - "personality": "", - "resume_display": "full", - # Recap tuning for /resume and startup resume. The defaults match the - # historical hardcoded values; expose them as config so power users can - # widen or tighten the snapshot to taste. - "resume_exchanges": 10, # max user+assistant pairs to show - "resume_max_user_chars": 300, # truncate user message text - "resume_max_assistant_chars": 200, # truncate non-last assistant text - "resume_max_assistant_lines": 3, # truncate non-last assistant lines - # When True (default), assistant entries that are *only* tool calls - # (no visible text) are skipped in the recap. This prevents the recap - # from being dominated by `[2 tool calls: terminal, read_file]` lines - # when an exchange was tool-heavy. Set False to restore the legacy - # behavior of showing tool-call summaries inline. - "resume_skip_tool_only": True, - "busy_input_mode": "interrupt", # interrupt | queue | steer - # When busy_input_mode="steer", suppress only the visible - # "Steered into current run" confirmation bubble by setting this false. - # The mid-turn steering itself still happens. - "busy_steer_ack_enabled": True, - # Which interface bare `hermes` (and `hermes chat`) launches by default: - # "cli" — the classic prompt_toolkit REPL (default, preserves prior behavior) - # "tui" — the modern Ink TUI (same as passing `--tui`) - # Explicit flags always win over this setting: `--cli` forces the classic - # REPL and `--tui` (or HERMES_TUI=1) forces the TUI regardless of config. - "interface": "cli", - # When true, `hermes --tui` auto-resumes the most recent human- - # facing session on launch instead of forging a fresh one. - # Mirrors `hermes -c` muscle memory. Default off so existing - # users aren't surprised. HERMES_TUI_RESUME= always wins. - "tui_auto_resume_recent": False, - # When true (default), `hermes --tui` drops a one-time hint - # ("subagents working · /agents to watch live") the first time a turn - # starts delegating, nudging the user toward the live spawn-tree - # dashboard. Set false to suppress the hint. - "tui_agents_nudge": True, - "bell_on_complete": False, - # Stream the model's reasoning/thinking live before the response. - # Default ON: on thinking models the reasoning phase can run tens of - # seconds, and with this off the user stares at a spinner the whole - # time even though tokens are streaming. Set false for quiet output. - "show_reasoning": True, - # When reasoning display is on, the post-response "Reasoning" recap box - # collapses long thinking to the first 10 lines. Set true to print the - # complete thinking text uncollapsed (live streaming is always full). - "reasoning_full": False, - # Background self-improvement review notifications surfaced in chat. - # "off" — no chat notification (the review still runs and writes) - # "on" — generic "💾 Memory updated" line (default) - # "verbose" — include a compact content preview of what changed - # Per-platform overrides via display.platforms..memory_notifications. - "memory_notifications": "on", - "streaming": False, - "timestamps": False, # Show timestamp on user and assistant labels - "timestamp_format": "%H:%M", # strftime format for timestamps (e.g. "%b-%d %H:%M") - "final_response_markdown": "strip", # render | strip | raw - # Preserve recent classic CLI output across Ctrl+L, /redraw, and - # terminal resize full-screen clears. Disable if a terminal emulator - # behaves badly with replayed scrollback. - "persistent_output": True, - "persistent_output_max_lines": 200, - # Print a one-line summary of resolved modal prompts (approval / - # clarify) into scrollback so the question and decision survive the - # panel repaint. Set false to keep scrollback untouched. - "persist_prompts": True, - "inline_diffs": True, # Show inline diff previews for write actions (write_file, patch, skill_manage) - # File-mutation verifier footer. When true (default), the agent - # appends a one-line advisory to its final response whenever a - # write_file / patch call failed during the turn and was never - # superseded by a successful write to the same path. This catches - # the "batch of parallel patches, half fail, model claims success" - # class of over-claim that otherwise forces users to run - # `git status` to verify edits landed. Set false to suppress. - "file_mutation_verifier": True, - # Nous credits status-bar notices (usage bands, grant-spent, depleted / - # restored). When false, no credits notices are emitted — balance data - # is still captured and /usage keeps working. Off switch for sub + - # top-up users who find the gauge noisy. - "credits_notices": True, - # Turn-completion explainer. When true (default), the agent appends a - # one-line explanation to its final response whenever a turn ends - # abnormally with no usable reply — empty content after retries, a - # partial/truncated stream, a still-pending tool result, or an - # iteration/budget limit. Replaces the bare "(empty)" sentinel so the - # failure isn't silent from the UI's perspective. Set false to suppress. - "turn_completion_explainer": True, - "show_cost": False, # Show $ cost in the status bar (off by default) - # Show a color-coded battery read-out as the first status-bar element in - # the CLI/TUI (off by default). No-op on machines without a battery. - "battery": False, - # Focus view (/focus): display-only reduced-output mode. When true the - # CLI/TUI pins tool_progress to "off" (reusing the existing suppression - # path), reports a per-turn hidden-line count with a recovery hint, and - # pins a "focus" segment in the status bar. focus_saved_tool_progress - # holds the mode /focus off restores. Never affects what is sent to the - # model — see hermes_cli/focus_view.py. - "focus_view": False, - "focus_saved_tool_progress": "all", - "skin": "default", - # UI language for static user-facing messages (approval prompts, a - # handful of gateway slash-command replies). Does NOT affect agent - # responses, log lines, tool outputs, or slash-command descriptions. - # Supported: en, zh, ja, de, es, fr, tr, uk. Unknown values fall back to en. - "language": "en", - # TUI busy indicator style: kaomoji (default), emoji, unicode (braille - # spinner), or ascii. Live-swappable via `/indicator