"""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"], # 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