"""Default configuration data for Hermes Agent. Pure-data leaf module: DEFAULT_CONFIG and OPTIONAL_ENV_VARS, extracted verbatim from hermes_cli/config.py. Must not import from hermes_cli.config. """ DEFAULT_CONFIG = { "model": "", "providers": {}, "fallback_providers": [], "credential_pool_strategies": {}, "toolsets": ["hermes-cli"], # SQLite journal mode used by every Hermes database opener. WAL is the # normal default; set DELETE for weak-fsync/shared filesystems where WAL is # not crash-safe (for example macOS virtiofs, NFS, or SMB). "database": { "journal_mode": "wal", # Optional WAL sizing pragmas, applied when set to integers. # None = SQLite defaults (autocheckpoint 1000 pages, no size limit). "wal_autocheckpoint": None, "journal_size_limit": None, }, # Soft file-descriptor limit for long-running Hermes server processes. # Clamped to the OS hard limit; 0/false/null disables the adjustment. "runtime": { "nofile_soft_limit": 4096, }, # 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, "session": { # Per-terminal `hermes -c`: each CLI session drops a breadcrumb file # under $HERMES_HOME/terminal-sessions/, and a bare # -c/--continue resumes THIS terminal's session (tmux pane, kitty # window, wezterm pane, plain tty, ...) instead of the globally # most-recent one. Set false to restore the old latest-session # behavior everywhere. "terminal_continue": True, }, "agent": { # Unlimited by default. The agent turn cap caused more problems than # it solved (silent mid-task truncation). null = unlimited; set a # positive integer to cap, or use "none"/"unlimited"/"inf"/0/-1 — # all normalized by hermes_cli.config.resolve_turn_limit. "max_turns": None, # Optional wall-clock budget in seconds per conversation run. # null/absent = feature fully off (zero behavior change). When set, # the agent gets a one-time wrap-up notice at 80% elapsed and # implicit provider stale timeouts are capped to the remaining # budget. CLI one-shot equivalent: `hermes chat --run-budget N`. "run_budget_seconds": None, # 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, # Maximum time an alias routing key waits for the active turn holding # the same resolved session lease. On expiry the inbound message is # rejected with a resend notice rather than run without serialization. # Keep this short: Telegram dispatches updates sequentially, so an # inline lease waiter also delays unrelated topics. Non-positive values # fall back to the five-second safety default. "gateway_turn_lease_timeout": 5, # Per-session AIAgent cache in the gateway. Each cached agent keeps a # warm prompt prefix AND the session's full transcript, so the cache # trades memory for cost: too small and every turn re-pays an uncached # prompt, too large and tool-heavy transcripts fill the heap. "agent_cache": { # LRU entry cap. "max_size": 128, # Evict an agent that has been idle this long (seconds). "idle_ttl_secs": 3600, # Anonymous-RSS budget (MB) above which the gateway starts shedding # least-recently-used transcripts, which reload from the persisted # session on the next turn. "auto" derives the budget from the # cgroup memory limit the gateway runs under (or total RAM when # uncapped); a number sets it explicitly; 0/off disables the pass # and lets memory grow to whatever the two bounds above allow. "memory_high_mb": "auto", # Upper bound on how many sessions one pressure pass sheds, so a # burst of teardowns cannot stall the gateway. "max_evictions_per_pass": 16, # Most-recently-used sessions the pressure pass never touches — # they are the ones actively paying for a warm prompt cache. "protect_recent": 8, }, # Force-interrupt budget once gateway stop()/drain has begun # (seconds). Applies to SIGTERM/external stop and to the final # phase of in-band restart after any after-turn wait. 0 = interrupt # immediately (the default). # # Keep this short and under systemd TimeoutStopSec — a long value # here invites SIGKILL-mid-cleanup. For in-band restart # (/restart, SIGUSR1), prefer restart_after_turn_timeout below so # active turns finish *before* stop() begins (#77184). "restart_drain_timeout": 0, # Cron-only floor under the stop()/drain wait (seconds). A chat turn # interrupted by a restart is announced to the user and resumed on # their next message; an interrupted cron run is written to jobs.json # as a permanent failure that nobody is waiting on, so it must not # inherit restart_drain_timeout's 0 (#82161). Clamped at runtime to # the shutdown-watchdog leash minus teardown headroom, so raising it # past ~50s has no effect unless TimeoutStopSec is raised too. # 0 = opt out (cron drains on restart_drain_timeout, legacy). "cron_drain_timeout": 30, # In-band restart wait for active turns to finish before stop() # (seconds). /restart and SIGUSR1 refuse new work, then wait up to # this cap for in-flight agents/cron/api runs to complete naturally # so the requesting turn is not amputated by restart_drain_timeout. # 0 = legacy behaviour (enter stop()/drain immediately). Default # 30 min is a safety valve for wedged agents, not a target latency — # an interactive `hermes gateway restart` must never block for hours # on a turn that wedged (#79133). Long unattended turns can raise # this in config.yaml. "restart_after_turn_timeout": 1800, # 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, # Empty-response retry guard (NS-503). The empty-retry loop # re-sends the full conversation input at full price on every # attempt; these settings stop it from re-billing *deterministic* # empties (unsignaled provider refusals with zero output tokens) # while failing open on any ambiguous evidence (missing usage, # any generated tokens, model/provider change mid-streak). "empty_response_guard": { # Master switch for both guards below. False restores the # legacy fixed 3-retry behaviour unconditionally. "enabled": True, # When the estimated input cost of a single empty attempt # meets or exceeds this many USD, the retry budget for the # streak drops from 3 to 1. Unknown pricing or missing usage # leaves the budget untouched. "cost_threshold_usd": 0.25, }, "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", # Execution-discipline guidance: injects a system prompt block covering # tool persistence, mandatory tool use for arithmetic/system facts, # external-write read-back, count reconciliation, literal preservation # of identifiers, and verification-gated completion. Chosen once at # session start keyed on model name (prompt stays byte-stable). # Values: "auto" (default — applies to gpt/codex/grok/deepseek/kimi/ # qwen/glm/minimax/mimo/mistral models), true/false (force on/off for # all models), or a list of model-name substrings to match. "execution_guidance": "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", # Runtime anti-stall guards. When True (default), two conservative # guards run: (1) an identical-call loop breaker that appends a short # notice to the tool result when the same tool is called 3+ consecutive # times with identical arguments AND identical results (never blocks; # pollers like `process` are exempt), and (2) a continue-intent # extension of the empty-response recovery that re-prompts once when # the model ends its turn saying it will continue but takes no action. # Set False to disable both. "stall_guards": True, # 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, # Bot Mode teammate-messaging protocol section (silent unless a # profile is managed by the desktop's Bot Mode). "bot_mode_protocol": 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 False (opt-in): # the v31/v32 config migrations already switch existing installs off # because the verification narrative proved more noise than signal, # and the docs tell users to treat off as the effective default — a # fresh install must not be the one population that still gets the # nudges. Set true to force on everywhere, or "auto" for the legacy # surface-aware behavior (on for interactive coding surfaces — CLI, # TUI, desktop — and programmatic callers, off for conversational # messaging surfaces). Doc/markdown/skill-only edits never fire it. "verify_on_stop": False, # 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, # Session stall watchdog (seconds). Scope (#76354): this is a # RECOVERY notifier for an in-process AIAgent that has an # adapter-queued follow-up (pending inbound / queued event) while its # activity clock is stale — NOT a general gateway/session stall # detector. It does not observe startup restoration, build sentinels, # turn leases, debounce state, or work owned by another process; the # scan cadence is per AIAgent instance, not globally coordinated per # durable session. Notify-only: warns the user to try /new. Distinct # from gateway_timeout (which kills the turn) and # gateway_notify_interval ("still working" heartbeats). 0 = disable. "session_stall_timeout": 300, # Long-lived reconnect-loop escalation (seconds). A platform that has # been continuously failing/reconnecting for this long gets # needs_attention flagged in gateway runtime status (visible in # `hermes status` / fleet monitoring). Retries never stop — this is a # signal, not a circuit breaker. 0 = disable. "reconnect_attention_after": 7200, # 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, # Max seconds the boot turn-machinery warm-up (#99373) may hold the # gateway's inbound gate shut. On a fresh boot the gateway warms the # agent-side turn prerequisites (run_agent import graph, tool schemas # + availability probes, context-file tier) BEFORE accepting inbound # messages, so a message seconds after boot is no longer served with # a skeleton system prompt (missing context files / tool schemas). # On timeout the gate opens anyway and warm-up finishes in the # background — a wedged init can't make the gateway permanently # unavailable. Set to 0 to disable the warm-up (historical # lazy-init behaviour). "gateway_startup_warmup_timeout": 20, # 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": {}, # Per-provider opt-in to preserve assistant ``reasoning_content`` # when replaying history. The built-in echo families (DeepSeek, # Kimi/Moonshot, Xiaomi MiMo) are auto-detected by provider name # and base-URL host. Custom providers and OpenAI-compatible # gateways that proxy those same models (or other thinking-mode # backends) are not covered by the host-based rules. # # Set ``reasoning_echo: true`` on a ``model:`` entry (primary) or a # ``fallback_providers:`` entry (per-fallback) to preserve # ``reasoning_content`` on replay for that provider only. Default # ``false`` keeps the historical strict-provider behavior (Mistral, # Groq, Cerebras reject the field with HTTP 400). "reasoning_echo": False, }, "terminal": { "backend": "local", "modal_mode": "auto", # Remote-backend graceful degradation: when a connection-class # infrastructure failure occurs (SSH host unreachable, Docker daemon # down), "warn" (default) returns a structured degraded tool result # with a reason + retry hint so the model can act on it; "fail" # preserves the historical error + traceback behavior. "degraded_mode": "warn", "cwd": ".", # Use current directory # Root directory for Hermes' terminal session temp files (background # logs/pid/exit files, code-execution sandboxes, etc.). When empty, # Hermes uses TMPDIR/TMP/TEMP if set, otherwise a managed dir on real # storage at HERMES_HOME/cache/terminal (auto-pruned after 72h) — NOT # tmpfs /tmp, which is RAM-capped and small on many distros (e.g. # Arch-based setups) and fills up under load. Set this to redirect # session temp anywhere else; must be an absolute POSIX path that # exists. User-set paths are never auto-pruned. "temp_dir": "", # Terminal font family for the desktop app's embedded xterm.js terminal. # When set (e.g. "'CaskaydiaCoveNerdFont', 'JetBrains Mono', monospace"), # the desktop terminal uses this as the CSS font-family value, with the # built-in default ("'JetBrains Mono', 'Cascadia Code', 'SF Mono', Menlo, # Consolas, monospace") as fallback when the field is empty or unset. # This lets users install a Nerd Font (or any custom font) and configure # it here without patching the built desktop app. "font_family": "", "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, # Bounded linger (seconds) for one-shot CLI runs (-q/-Q/-z) that exit # while background processes spawned with notify_on_complete=true are # still running. The dying parent owns those children's stdout pipes, # so exiting immediately kills the delivery a few seconds later — # destroying Bot Mode handoff replies dispatched via message_agent / # bot_relay from a short-lived `hermes -p chat -Q` recipient # (#90879). The parent instead waits (up to this bound) for tracked # notify_on_complete processes to finish before exiting. Plain # background processes without notify_on_complete (servers, daemons) # are never waited on. 0 disables the linger. "oneshot_completion_wait_seconds": 600.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", # Vercel Sandbox runtime (vercel_sandbox backend only). # Supported: node24, node22, python3.13. "vercel_runtime": "node24", # Container resource limits (docker, singularity, modal, daytona, vercel_sandbox — 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 # /dev/shm size for the Docker sandbox. Docker's 64 MB default silently # breaks Chromium/Playwright and PyTorch DataLoader workers; tmpfs is # lazily allocated so the higher ceiling costs nothing until used. # Set to "" (or "0") to omit the flag and use Docker's default. "docker_shm_size": "1g", # 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, # Explicit opt-in for trusted profiles to reuse the same Docker # container identity. Empty preserves the active-profile boundary. "docker_shared_container_key": "", # 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 # Keyless free-tier ring: with NO web backend configured or keyed, # web_search/web_extract rotate round-robin across five vendors' # public free tiers (exa, parallel, firecrawl, keenable), # failing over to the next ring vendor on rate limits. Never # pre-empts a configured or keyed backend. Set false to disable. "keyless_fallback": True, # One-shot keyless rescue: when the chosen/keyed backend fails a # web_search/web_extract call, THAT call retries once on the keyless # free-tier ring — the next call attempts the chosen backend again # (no sticky failover). Off when keyless_fallback is false. "keyless_rescue": True, # Per-provider tier selection for ring vendors with both a keyless # free endpoint and a keyed paid path (exa, parallel, # firecrawl, keenable). Set by the `hermes tools` picker's # "Free (keyless)" / "Paid (API key)" rows. # free — always use the anonymous free endpoint (even with a key) # paid — always use the keyed path (missing key = error; vendor # is also excluded from the keyless ring) # unset — auto: keyed when the API key is present, else the ring "provider_tier": {}, # TTL result caching for web_search + web_extract. Repeat searches # (same query, same provider) within the TTL are served from an # in-process memo; repeat extracts of the same URL are served from # the cache/web full-text store. Concurrent identical searches # (parallel subagents) coalesce into one vendor request. Only # successful responses are cached. "cache_enabled": True, "cache_ttl_minutes": 20, # Hosts whose pages must always be fetched live, never from the # extract cache — sites you're actively developing but testing over # the public internet (staging deploys, tunnel URLs, preview # builds). Entries match exactly, as "*.wildcard", or as a domain # suffix ("mysite.dev" also covers "preview.mysite.dev"). # localhost/private-IP URLs are always exempt automatically. # cache_exempt_hosts: ["mysite.vercel.app", "*.ngrok-free.app"] "cache_exempt_hosts": [], }, "browser": { # Browser tool implementation. # "" — DEFAULT: Browser Use mode when the browser-use CLI # (or uvx) is available; otherwise the built-in # browser tools. Camofox setups always keep the # built-in tools (no CDP surface). # "browser-use" — force Browser Use mode: one browser_exec tool # driving the Browser Use CLI 3.0 over any CDP # backend (local Chrome, cloud browsers) # "off" — force the built-in browser tools # (browser_navigate, browser_click, …) "backend": "", "inactivity_timeout": 120, "command_timeout": 30, # Timeout for browser commands in seconds (screenshot, navigate, etc.) "snapshot_threshold": 15000, # Max chars before snapshot truncate-and-store (min 1000) "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.) # Local browser engine, for both drivers: # Browser Use mode (default) — "lightpanda" makes Hermes spawn # ``lightpanda serve`` per session and point browser_exec at it. # Built-in tools (backend: off) — passed as ``--engine `` to # agent-browser v0.25.3+ (with automatic Chrome fallback). # "auto" — Chrome (default) # "lightpanda" — Lightpanda (faster navigation, no screenshots) # "chrome" — explicitly request Chrome # Ignored while a cloud provider, Camofox, browser.cdp_url or # browser.use_real_profile is active — `/browser status` and # `hermes doctor` say so. Also settable via AGENT_BROWSER_ENGINE. "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 # Consent to browse with the user's REAL logins for local browsing. # When true, local browsing (the Browser Use CLI, or the built-in # browser tools) runs on a Hermes-managed SNAPSHOT of the user's # ACTIVE default-Chromium profile (Local State -> profile.last_used) — # its cookies, logins and preferences copied in and re-synced when a # fresh session launches — driven by Hermes' packaged Chromium. Only # the active profile is copied. The snapshot is a non-default dir, so it # sidesteps Chrome 136+'s block on debugging the default profile and # never contends with the user's running browser. Turning this back off # deletes the snapshot store (~/.hermes/browser-profile/) so copied # credentials don't outlive consent. Only Chromium-family default # browsers are supported (Chrome, Edge, Brave, Brave Origin, Chromium); a non-Chromium # default (e.g. Firefox) fails closed with a clear message. Default # false. Also gates the browser_exec ``local`` argument, which forces a # real-profile local session even under a cloud browser backend. Toggle # in the desktop Settings → Browser section. "use_real_profile": False, # When real-profile browsing needs the browser closed (Windows: a # running Chrome/Edge/Brave locks its cookie DB deny-all, so it must be # fully quit before its profile can be copied), arm the "offer to close # it" flow. This does NOT auto-kill: when the profile is locked the # snapshot always blocks and the agent asks the user first; only on # approval does it run `hermes browser close-profile` (terminates the # browser process tree bound to that profile, losing unsaved tabs) and # retry. Still locked afterward → stays blocked, no loop, no auto-kill. # OFF by default. No effect on macOS/Linux (copy-while-running works). "real_profile_autoclose": False, # Pin WHICH source browser profile directory gets snapshotted for # real-profile browsing (e.g. "Profile 2"). Unset/empty: follows the # browser's last-used profile (Local State → profile.last_used). On a # machine with several profiles (work + personal), last-used roulette # can silently hand the agent the wrong identity; a pin locks it. A pin # naming a directory that doesn't exist FAILS CLOSED with a fixable # message rather than falling back to last-used. "real_profile_pin": "", "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", }, # Authenticated browser-extension controller lane. When enabled, an # extension that registers through the gateway can become the exact # controller for a session's browser_* tools (fail-closed once bound). # Local API registration additionally requires the API server bearer # key. developer_mode gates the privileged capabilities # (browser_cdp / browser_evaluate) — never negotiable without it. "extension_control": { "enabled": False, "developer_mode": False, }, }, # 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, # Single-query (``hermes -q/-z "..."``) variant of mcp_discovery_timeout. # In one-shot mode there is only ONE turn, so the between-turns late-binding # refresh never runs: a server that misses the small interactive bound is # invisible to the LLM for the whole session. This larger bound gives slow # cold-start servers (npx, uvx, remote HTTP) a chance to land in the one # tool snapshot. ``thread.join(timeout)`` returns the instant discovery # completes, so reachable servers only wait for their real handshake time # while unavailable servers remain bounded. "mcp_single_query_discovery_timeout": 15.0, # 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, "checkpoint_required": False, # Fail closed before lossy compaction unless an # active memory provider confirms checkpoint API # compatibility and completes the checkpoint. "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 "tail_mode": "lean", # tail retention policy (#87326): # "lean" — clamped 2.5%-of-window tail (default) # (10K floor / 25K cap) plus chunked # digests, a mechanical anchor index, # verbatim user messages, and # session_search recovery pointers in # the summary. ~3x fewer retained # tokens after compaction; costs a few # extra summarizer calls at the # compaction boundary. # "legacy" — pre-#87326 0.20×threshold verbatim # tail (100-240K tokens on big-window # or raised-threshold setups). "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), then waits # for a full trigger-sized token runway to # regrow before rearming. Keeps prompt-cache # breaks episodic. 0 = no minimum-savings gate. "micro_compact": False, # opt-in: after each completed turn, fold the # oldest un-absorbed exchange into a rolling # summary, amortizing compression cost instead # of paying it in one batch stall. Default False # because a pass rewrites already-sent history # and so breaks the provider prompt-cache prefix # EVERY turn — the per-turn cache break that # `proactive_prune_min_reclaim_tokens` above # exists to avoid. Enable only when you have # measured that the amortized stall is worth # more to you than the cached-prefix discount. # See docs/micro-compaction.md. "micro_compact_every_n_turns": 1, # cadence: run a pass every Nth completed # turn. Since each pass costs one prompt-cache # break, this is the dial for how often that # cost is paid — 1 reclaims most aggressively # at one break per turn, 5 trades reclaim rate # for a fifth of the breaks. Clamped to >= 1. # Ignored unless `micro_compact` is true. "micro_compact_defrag_threshold_tokens": 2000, # once the rolling summary # exceeds this many tokens, the next pass # re-summarizes the summary itself instead of # letting it grow without bound. "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 "hygiene_max_turn_hold_seconds": 10, # max seconds an ARRIVING user turn is held while a # still-streaming hygiene summary finishes. Distinct from # hygiene_timeout_seconds (compressor inactivity budget): # this bounds user-visible latency once real input is # waiting. Kept well under chat-transport idle timeouts # (Telegram ~30s). On expiry the turn proceeds # uncompressed — an availability boundary, not a failure. "context_timeout_seconds": 120, # inactivity budget for in-agent compress_context # (conversation loop, /compress, preflight, etc.). # Same progress-aware semantics as hygiene_timeout_seconds: # streamed summary tokens extend the wait; only a silent # worker is cut off. 0 = disable the owned wrapper # (callers that already pass commit_fence, e.g. gateway # hygiene, never use this path). "context_total_ceiling_seconds": 600, # absolute cap on the *pre-commit* # in-agent compress_context wait (summary / # stream phase) even while tokens are still # moving. Clamped to >= context_timeout_seconds # when the idle budget is > 0. Guarantee: # the summary phase is bounded by this # ceiling; an already-started SessionDB # commit is never abandoned mid-flight — # if the commit itself runs past the # ceiling it is logged (WARNING, then # ERROR) and surfaced to the user via the # warning channel while the host keeps # waiting in bounded increments for the # commit to finish. "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). "codex_responses_native": False, # Opt in to OpenAI's server-side compaction # on the Responses API. Engages ONLY for # gpt-5.6-family models on api.openai.com or # the ChatGPT Codex backend; every other # route/model is unaffected. Hermes' local # compression stays armed as the fallback. "codex_responses_compact_threshold": None, # Optional absolute server compaction # trigger in input tokens. None follows the # resolved local compression trigger with a # safety margin. Explicit values only clamp # downward so the server compacts first. "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: "5m" or "1h" (Anthropic-supported tiers). Other non-falsy # values are silently ignored. Falsy values (false, null, "off", # "disabled", "no", "none") disable prompt caching entirely. "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, # Restrict the auxiliary auto-chain's OpenRouter fallback to free # (:free) SKUs. When true, the OpenRouter step is skipped entirely # unless the resolved fallback model ends in ":free" — a PAID lane # is never engaged for background auxiliary traffic (compression, # title generation, session search, vision, web extract) even when # OPENROUTER_API_KEY is present. Default false keeps the historical # paid fallback for users who want it. "free_only": False, # Override the auxiliary auto-chain's OpenRouter fallback model # (default: google/gemini-3.6-flash, a PAID model). Set e.g. # "nvidia/nemotron-3-ultra-550b-a55b:free" together with # free_only: true to keep auxiliary traffic free-only. A one-time # WARNING is logged whenever a non-":free" model is engaged. "openrouter_model": "", # 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 }, # Note: web_extract no longer uses an auxiliary LLM — pages are # truncate-and-stored with a read_file pointer (no summarization), # and browser snapshots follow the same pattern. The old # ``auxiliary.web_extract.*`` block was removed here. Existing # values in user config.yaml files are harmless leftovers and ignored. "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) # Guarded fast lane: only honored with a concrete provider/model # and an explicit ``reasoning_effort: none`` certification. # Zero preserves the historic uncapped compression request. "max_output_tokens": 0, }, # 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) }, # /review — the independent reviewer subagent's model. Unlike other # aux tasks this is not a single LLM call: the reviewer is a full # subagent (all normal subagent tools) spawned on the async # delegation rail. provider/model/base_url/api_key/api_mode are # resolved through the same credential system as delegation.provider # pins. Leave provider "auto" + model empty to run the reviewer on # the main agent's model. "review": { "provider": "auto", # auto (= inherit main model) | openrouter | nous | anthropic | ... "model": "", # e.g. "anthropic/claude-opus-4.6" — a strong reviewer model "base_url": "", # direct OpenAI-compatible endpoint (takes precedence over provider) "api_key": "", # API key for base_url / provider override "api_mode": "", # force transport: chat_completions | anthropic_messages | codex_responses }, "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": "", "prefer_fast_model": False, # opt in to provider fast tier; auto otherwise uses the main 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": { # Master switch for automatic post-turn memory/skill review forks. # false = skip automatic spawns (manual /refine still works). "enabled": True, "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) # Aggregate INPUT-token budget for one review fork (issue #93057). # The fork's FIRST request replays the full snapshot as a warm # prompt-cache read (compaction is deferred until the first # provider response arrives); after that it compacts an oversized # snapshot in memory before further provider calls. This caps the # SUM of input tokens replayed across the whole review tool loop # (iterations are separately capped at 16). The loop stops before # the provider call that would cross the budget. 0 or a negative # value = unlimited. "max_input_tokens": 600000, }, "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, # Classic CLI multiline fallbacks beyond Alt+Enter. # Default true matches Claude Code / Codex / OpenCode: Ctrl+J inserts # a newline, a trailing backslash followed by Enter continues the draft, # and supported terminals are asked to report Shift+Enter distinctly. # Set false to restore the legacy c-j submit fallback on unusual POSIX # PTYs whose plain Enter arrives as LF instead of CR. "cli_multiline_shortcuts": 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", # Gateway notifications when a terminal(background=true) process # finishes: # "concise" — one-line status message; failures append a short # output tail (default) # "all" — running-output updates + final raw-output message # "result" — final raw-output message only # "error" — final raw-output message only on non-zero exit # "off" — no watcher messages at all "background_process_notifications": "concise", "streaming": False, "timestamps": False, # Show message timestamps (CLI labels, TUI rows, desktop transcript) "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, # Clear terminal scrollback as well as the visible viewport when the # classic CLI performs a full redraw/resize recovery. Disabled by # default because some users prefer preserving terminal history; # enable when a terminal/tmux stack stamps stale prompt chrome into # scrollback during fullscreen/restore window transitions. "cli_rebuild_scrollback_on_redraw": False, # 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