# Hermes Agent CLI Configuration # Copy settings from this example into ~/.hermes/config.yaml, or use # `hermes config set ` to update the active profile. # This file configures CLI behavior; only documented secret environment # variables in .env take precedence over their corresponding settings. # ============================================================================= # Database Configuration # ============================================================================= # WAL is the normal default. Hermes automatically falls back to DELETE when # SQLite reports that WAL is incompatible with the filesystem. Set this to # "delete" explicitly for deployments whose backing filesystem is not WAL # crash-safe, such as Linux containers bind-mounted through macOS virtiofs, # NFS, or SMB. Hermes will not live-downgrade a database already open in WAL. database: journal_mode: "wal" # Supported values: "wal", "delete" # Optional WAL sizing pragmas (integers). Unset = SQLite defaults. # wal_autocheckpoint: 1000 # pages between automatic checkpoints # journal_size_limit: 67108864 # cap the WAL/journal file size in bytes # # Durability level for every state.db connection: OFF, NORMAL, FULL, EXTRA # (or 0-3). Unset leaves SQLite's default, which is baked in at compile time # (SQLITE_DEFAULT_WAL_SYNCHRONOUS) and therefore differs between the bundled # interpreter, a distro python3 and a Homebrew one. Set it if you need to # know which one you are running rather than infer it. On macOS this is a # floor, not a pin: values below FULL are refused because Darwin's fsync() # does not guarantee write ordering, while EXTRA is honored normally. # synchronous: FULL # ============================================================================= # Runtime Limits # ============================================================================= # Long-running Hermes server processes raise their RLIMIT_NOFILE soft limit to # this value when the operating system permits it. The value is clamped to the # hard limit and never lowers an already higher soft limit. Set to 0, false, or # null to disable the adjustment. Default: 4096. runtime: nofile_soft_limit: 4096 # ============================================================================= # Model Configuration # ============================================================================= model: # Default model to use (can be overridden with --model flag) # Both "default" and "model" work as the key name here. default: "anthropic/claude-opus-4.6" # Inference provider selection: # "auto" - Auto-detect from credentials (default) # "openrouter" - OpenRouter (requires: OPENROUTER_API_KEY or OPENAI_API_KEY) # "nous" - Nous Portal OAuth (requires: hermes auth add nous) # "nous-api" - Nous Portal API key (requires: NOUS_API_KEY) # "anthropic" - Direct Anthropic API (requires: ANTHROPIC_API_KEY) # "openai-codex" - OpenAI Codex (requires: hermes auth) # "copilot" - GitHub Copilot / GitHub Models (requires: GITHUB_TOKEN) # "gemini" - Use Google AI Studio direct (requires: GOOGLE_API_KEY or GEMINI_API_KEY) # "zai" - Use z.ai / ZhipuAI GLM models (requires: GLM_API_KEY) # "kimi-coding" - Kimi / Moonshot AI (requires: KIMI_API_KEY) # "minimax" - MiniMax global (requires: MINIMAX_API_KEY) # "minimax-cn" - MiniMax China (requires: MINIMAX_CN_API_KEY) # "huggingface" - Hugging Face Inference (requires: HF_TOKEN) # "nvidia" - NVIDIA NIM / build.nvidia.com (requires: NVIDIA_API_KEY) # "xiaomi" - Xiaomi MiMo (requires: XIAOMI_API_KEY) # "arcee" - Arcee AI Trinity models (requires: ARCEEAI_API_KEY) # "ollama-cloud" - Ollama Cloud (requires: OLLAMA_API_KEY — https://ollama.com/settings) # "deepinfra" - DeepInfra (requires: DEEPINFRA_API_KEY) # "kilocode" - KiloCode gateway (requires: KILOCODE_API_KEY) # "ai-gateway" - Vercel AI Gateway (requires: AI_GATEWAY_API_KEY) # "azure-foundry" - Microsoft Foundry / Azure OpenAI (API key or Entra ID) # "lmstudio" - LM Studio local server (optional: LM_API_KEY, defaults to http://127.0.0.1:1234/v1) # # Local servers (LM Studio, Ollama, vLLM, llama.cpp): # "custom" - Any other OpenAI-compatible endpoint. Set base_url below. # Aliases: "ollama", "vllm", "llamacpp" all map to "custom". # LM Studio is first-class and uses provider: "lmstudio". # It works with both no-auth and auth-enabled server modes. # # Can also be overridden for a single invocation with the --provider flag. provider: "auto" # API configuration (falls back to OPENROUTER_API_KEY env var) # api_key: "your-key-here" # Uncomment to set here instead of .env base_url: "https://openrouter.ai/api/v1" # Azure Foundry keyless auth example: # provider: "azure-foundry" # base_url: "https://.openai.azure.com/openai/v1" # auth_mode: "entra_id" # DefaultAzureCredential: az login, managed identity, workload identity, etc. # default: "gpt-4o" # Deployment/model name # entra: # scope: "https://ai.azure.com/.default" # Optional; this is the default. # ── Token limits — two settings, easy to confuse ────────────────────────── # # context_length: TOTAL context window (input + output tokens combined). # Controls when Hermes compresses history and validates requests. # Leave unset — Hermes auto-detects the correct value from the provider. # Set manually only when auto-detection is wrong (e.g. a local server with # a custom num_ctx, or a proxy that doesn't expose /v1/models). # # context_length: 131072 # # max_tokens: OUTPUT cap — maximum tokens the model may generate per response. # Unrelated to how long your conversation history can be. # The OpenAI-standard name "max_tokens" is a misnomer; Anthropic's native # API has since renamed it "max_output_tokens" for clarity. # Leave unset to use the model's native output ceiling (recommended). # Set only if you want to deliberately limit individual response length. # # max_tokens: 8192 # ── Custom request headers (optional) ───────────────────────────────────── # # default_headers: extra HTTP headers sent on every request to an # OpenAI-compatible endpoint. User values take precedence over the # provider/SDK defaults, so this is the supported way to override the # OpenAI Python SDK's identifying headers (User-Agent: OpenAI/Python ..., # X-Stainless-*) when a custom provider sits behind a gateway/WAF that # rejects them — e.g. an upstream that returns "502 Upstream access # forbidden" for the SDK default User-Agent but accepts a plain one. # Applies on the OpenAI wire only (not native Anthropic / Bedrock). # # default_headers: # User-Agent: "curl/8.7.1" # # extra_headers: accepted as an alias of default_headers (merged, with # extra_headers winning when both are set) — matches the per-provider # extra_headers key below. # # Per-provider variant: named providers / custom_providers entries accept an # extra_headers dict scoped to that endpoint only — for reverse proxies, # gateways, or custom auth (e.g. Cloudflare Access service tokens). # Merged onto SDK/provider defaults with the entry's values winning. # Header values are treated as secrets and are never logged. # # providers: # my-proxy: # base_url: "https://llm.internal.example.com/v1" # key_env: "MY_PROXY_API_KEY" # extra_headers: # CF-Access-Client-Id: "xxxx.access" # CF-Access-Client-Secret: "${CF_ACCESS_SECRET}" # X-Client-Name: "hermes-agent" # providers: # meta: # base_url: https://api.meta.ai/v1 # api_key: ${MODEL_API_KEY} # # api_mode auto-detected as codex_responses for api.meta.ai; no need to set # # (the bundled meta-ai provider covers this — a named custom provider is # # only needed for a non-default Meta-compatible endpoint) # Command-minted credentials (optional): key_cmd # ------------------------------------------------------------------ # Enterprise gateways often issue SHORT-LIVED bearers (SSO/OIDC brokers, cloud # IAM, internal auth proxies) rather than static API keys, so a value copied # into .env via `key_env` is stale within the hour and every later request 401s. # `key_cmd` names a command that PRINTS a token instead: Hermes runs it per # request (cached until shortly before expiry), so long sessions keep working # with no restart. # # Contract: print ONLY the token on stdout, either bare or as JSON with an # "access_token" field ("expires_in" is honoured). Same shape as OAuth 2.0 # token endpoints, Claude Code's `apiKeyHelper`, `gcloud auth # print-access-token`, and `aws ecr get-login-password`. # # Precedence: an explicit --api-key still wins; otherwise key_cmd is preferred # over inline api_key / key_env on that entry. # # Applies to the main agent turn and to auxiliary tasks (title generation, # context compression, vision, embedding) alike. # # Not to be confused with `secrets.command`, which is a different mechanism: # that one runs a helper ONCE at startup to populate env vars for many secrets # at the process level. Use it for a vault or keychain helper that hands back a # KEY=VALUE blob. Use `key_cmd` when ONE provider needs a credential refreshed # DURING a session, because a startup-time env var cannot be re-minted after it # expires. # # providers: # my-gateway: # base_url: "https://gateway.internal.example.com/v1" # api_mode: chat_completions # key_cmd: "my-auth-cli print-token --profile prod" # # Worked example — an AI gateway that routes by model family, so one entry per # wire format shares the same credential helper: # # providers: # dbx: # OpenAI-compatible (MLflow) route # base_url: "https://.cloud.databricks.com/ai-gateway/mlflow/v1" # api_mode: chat_completions # model: databricks-claude-sonnet-4-6 # key_cmd: "databricks auth token -p MY-PROFILE" # dbx-gpt: # OpenAI Responses route # base_url: "https://.cloud.databricks.com/ai-gateway/openai/v1" # api_mode: codex_responses # model: databricks-gpt-5-5 # key_cmd: "databricks auth token -p MY-PROFILE" # dbx-claude: # Anthropic Messages route # base_url: "https://.cloud.databricks.com/ai-gateway/anthropic" # api_mode: anthropic_messages # model: databricks-claude-fable-5 # key_cmd: "databricks auth token -p MY-PROFILE" # Named provider overrides (optional) # Use this for per-provider request timeouts, non-stream stale timeouts, # and per-model exceptions. # Applies to the primary turn client on every api_mode (OpenAI-wire, native # Anthropic, and Anthropic-compatible providers), the fallback chain, and # client rebuilds during credential rotation. For OpenAI-wire chat # completions (streaming and non-streaming) the configured value is also # used as the per-request ``timeout=`` kwarg so it wins over the legacy # HERMES_API_TIMEOUT env var (which still applies when no config is set). # ``stale_timeout_seconds`` controls the non-streaming stale-call detector and # wins over the legacy HERMES_API_CALL_STALE_TIMEOUT env var. Leaving these # unset keeps the legacy defaults (HERMES_API_TIMEOUT=1800s, # HERMES_API_CALL_STALE_TIMEOUT=90s, native Anthropic 900s). The # implicit non-stream stale detector is auto-disabled for local endpoints # and can scale upward for very large contexts. # # Not currently wired for AWS Bedrock (bedrock_converse + AnthropicBedrock # SDK paths) — those use boto3 with its own timeout configuration. # # providers: # ollama-local: # request_timeout_seconds: 300 # Longer timeout for local cold-starts # stale_timeout_seconds: 900 # Explicitly re-enable stale detection on local endpoints # anthropic: # request_timeout_seconds: 30 # Fast-fail cloud requests # models: # claude-opus-4.6: # timeout_seconds: 600 # Longer timeout for extended-thinking Opus calls # openai-codex: # models: # gpt-5.4: # stale_timeout_seconds: 1800 # Longer non-stream stale timeout for slow large-context turns # ============================================================================= # Unified Timeouts (operation deadlines) # ============================================================================= # One place to override Hermes's internal operation deadlines (seconds). # Keys are dotted paths resolved by agent/deadline.py:resolve_timeout(). # Precedence: this section > legacy HERMES_* env var (back-compat) > built-in # default. 0 or a negative value disables the bound (unbounded); very large # values are clamped to a platform-safe maximum automatically. # # Currently resolved keys (more paths migrate here over time — see issue #85125): # # timeouts: # tools: # concurrent_batch: 420 # Deadline for a parallel tool-call batch # # (legacy env: HERMES_CONCURRENT_TOOL_TIMEOUT_S) # sequential_call: 420 # Deadline for one sequentially-executed tool call. # # Defaults to concurrent_batch's value so the two # # executor paths stay in sync; human waits # # (approval prompts, clarify) never count against it. # ============================================================================= # OpenRouter Provider Routing (only applies when using OpenRouter) # ============================================================================= # Control how requests are routed across providers on OpenRouter. # See: https://openrouter.ai/docs/guides/routing/provider-selection # # provider_routing: # # Sort strategy: "price" (default), "throughput", or "latency" # # Append :nitro to model name for a shortcut to throughput sorting. # sort: "throughput" # # # Only allow these providers (provider slugs from OpenRouter) # # only: ["anthropic", "google"] # # # Skip these providers entirely # # ignore: ["deepinfra", "fireworks"] # # # Try providers in this order (overrides default load balancing) # # order: ["anthropic", "google", "together"] # # # Require providers to support all parameters in your request # # require_parameters: true # # # Data policy: "allow" (default) or "deny" to exclude providers that may store data # # data_collection: "deny" # ============================================================================= # OpenRouter Response Caching (only applies when using OpenRouter) # ============================================================================= # Cache identical API responses at the OpenRouter edge for free instant replays. # When enabled, identical requests (same model, messages, parameters) return # cached responses with zero billing. Separate from Anthropic prompt caching. # See: https://openrouter.ai/docs/guides/features/response-caching # # openrouter: # response_cache: true # Enable response caching (default: true) # response_cache_ttl: 300 # Cache TTL in seconds, 1-86400 (default: 300) # ============================================================================= # Git Worktree Isolation # ============================================================================= # When enabled, each CLI session creates an isolated git worktree so multiple # agents can work on the same repo concurrently without file collisions. # Equivalent to always passing --worktree / -w on the command line. # # worktree: true # Always create a worktree when in a git repo # worktree: false # Default — only create when -w flag is passed # # By default a new worktree branches from the freshly-fetched remote tip # (the current branch's upstream, else the remote's default branch) so it # starts current with the project instead of from the local clone's # (possibly stale) HEAD. Set worktree_sync: false to branch from local HEAD # instead — useful when offline or when you deliberately want the clone's # exact current state as the base. # # worktree_sync: true # Default — branch from the fetched remote tip # worktree_sync: false # Branch from local HEAD (offline / pinned base) # ============================================================================= # Kanban Review Dispatch # ============================================================================= # First-class review tasks are dispatched automatically by default. The worker # is spawned with the bundled sdlc-review skill and can approve, request changes # back to the original implementer, or escalate a genuine external blocker. # Disable this only when every review is performed manually from the dashboard. kanban: review_dispatch: true # ============================================================================= # Terminal Tool Configuration # ============================================================================= # Choose ONE of the following terminal configurations by uncommenting it. # The terminal tool executes commands in the specified environment. # ----------------------------------------------------------------------------- # OPTION 1: Local execution (default) # Commands run directly on your machine in the current directory # ----------------------------------------------------------------------------- # Working directory behavior: # - CLI (`hermes` command): Uses "." (current directory where you run hermes) # - Gateway/messaging/cron: Uses terminal.cwd here; legacy .env cwd values are deprecated terminal: backend: "local" cwd: "." # For local backend: "." = current directory. Ignored for remote backends unless a backend documents otherwise. # Desktop xterm font. Install the font locally; Nerd Fonts render Powerlevel10k glyphs. # font_family: "MesloLGS NF" # Also accepts a CSS stack; blank uses bundled JetBrains Mono. timeout: 180 # HOME policy for tool subprocesses: # auto - default: host uses your real HOME; containers use HERMES_HOME/home # real - force your real OS-user HOME # profile - force HERMES_HOME/home for strict per-profile CLI config isolation home_mode: "auto" docker_mount_cwd_to_workspace: false # SECURITY: off by default. Opt in to mount the launch cwd into Docker /workspace. lifetime_seconds: 300 # sudo_password: "hunter2" # Optional: pipe a sudo password via sudo -S. SECURITY WARNING: plaintext. # sudo_password: "" # Explicit empty password: try empty and never open the interactive sudo prompt. # ----------------------------------------------------------------------------- # OPTION 2: SSH remote execution # Commands run on a remote server - agent code stays local (sandboxed) # Great for: keeping agent isolated from its own code, using powerful remote hardware # ----------------------------------------------------------------------------- # terminal: # backend: "ssh" # cwd: "/home/myuser/project" # Path on the REMOTE server # timeout: 180 # lifetime_seconds: 300 # ssh_host: "my-server.example.com" # ssh_user: "myuser" # ssh_port: 22 # ssh_key: "~/.ssh/id_rsa" # Optional - uses ssh-agent if not specified # ----------------------------------------------------------------------------- # OPTION 3: Docker container # Commands run in an isolated Docker container # Great for: reproducible environments, testing, isolation # ----------------------------------------------------------------------------- # terminal: # backend: "docker" # cwd: "/workspace" # Path INSIDE the container (default: /) # timeout: 180 # lifetime_seconds: 300 # docker_image: "nikolaik/python-nodejs:python3.11-nodejs20" # docker_mount_cwd_to_workspace: true # Explicit opt-in: mount your launch cwd into /workspace # # Optional: run the container as your host user's uid:gid so files written # # into bind-mounted dirs are owned by you, not root. Drops SETUID/SETGID # # caps too since no gosu privilege drop is needed. Leave off if your # # chosen docker_image expects to start as root. # docker_run_as_host_user: true # # Optional: explicitly forward selected env vars into Docker. # # These values come from your current shell first, then ~/.hermes/.env. # # Warning: anything forwarded here is visible to commands run in the container. # docker_forward_env: # - "GITHUB_TOKEN" # - "NPM_TOKEN" # # Optional: extra flags passed verbatim to docker run (appended after security defaults). # # Useful for adding capabilities (e.g. apt installs needing SETUID) or custom options. # # Example: add a Linux capability not included by default # # docker_extra_args: # # - "--cap-add" # # - "SETUID" # ----------------------------------------------------------------------------- # OPTION 4: Singularity/Apptainer container # Commands run in a Singularity container (common in HPC environments) # Great for: HPC clusters, shared compute environments # ----------------------------------------------------------------------------- # terminal: # backend: "singularity" # cwd: "/workspace" # Path INSIDE the container (default: /root) # timeout: 180 # lifetime_seconds: 300 # singularity_image: "docker://nikolaik/python-nodejs:python3.11-nodejs20" # ----------------------------------------------------------------------------- # OPTION 5: Modal cloud execution # Commands run on Modal's cloud infrastructure # Great for: GPU access, scalable compute, serverless execution # ----------------------------------------------------------------------------- # terminal: # backend: "modal" # cwd: "/workspace" # Path INSIDE the sandbox (default: /root) # timeout: 180 # lifetime_seconds: 300 # modal_image: "nikolaik/python-nodejs:python3.11-nodejs20" # ----------------------------------------------------------------------------- # OPTION 6: Daytona cloud execution # Commands run in Daytona cloud sandboxes # Great for: Cloud dev environments, persistent workspaces, team collaboration # Requires: pip install daytona, DAYTONA_API_KEY env var # ----------------------------------------------------------------------------- # terminal: # backend: "daytona" # cwd: "~" # timeout: 180 # lifetime_seconds: 300 # daytona_image: "nikolaik/python-nodejs:python3.11-nodejs20" # container_disk: 10240 # Daytona max is 10GB per sandbox # # --- Container resource limits (docker, singularity, modal, daytona -- ignored for local/ssh) --- # These settings apply to all container backends. They control the resources # allocated to the sandbox and whether its filesystem persists across sessions. container_cpu: 1 # CPU cores container_memory: 5120 # Memory in MB (5120 = 5GB) container_disk: 51200 # Disk in MB (51200 = 50GB) container_persistent: true # Persist filesystem across sessions (false = ephemeral) # ----------------------------------------------------------------------------- # SUDO SUPPORT (works with ALL backends above) # ----------------------------------------------------------------------------- # Add sudo_password to any terminal config above to enable sudo commands. # The password is piped via `sudo -S`. Works with local, ssh, docker, etc. # # SECURITY WARNING: Password stored in plaintext! # # INTERACTIVE PROMPT: If sudo_password is unset and the CLI is running, # you'll be prompted to enter your password when sudo is needed: # - 45-second timeout (auto-skips if no input) # - Press Enter to skip (command fails gracefully) # - Password is hidden while typing # - Password is cached for the session # # EMPTY PASSWORDS: Setting sudo_password to an explicit empty string is different # from leaving it unset. Hermes will try an empty password via `sudo -S` and # will not open the interactive prompt. This is useful for passwordless sudo, # Touch ID sudo setups, and environments where prompting is just noise. # # ALTERNATIVES: # - SSH backend: Configure passwordless sudo on the remote server # - Containers: Run as root inside the container (no sudo needed) # - Local: Configure /etc/sudoers for specific commands # # Example (add to your terminal section): # sudo_password: "your-password-here" # ============================================================================= # Security Scanning (tirith) # ============================================================================= # Optional pre-exec command security scanning via tirith. # Detects homograph URLs, pipe-to-shell, terminal injection, env manipulation. # Install: brew install sheeki03/tap/tirith # Docs: https://github.com/sheeki03/tirith # # security: # tirith_enabled: true # Enable/disable tirith scanning # tirith_path: "tirith" # Path to tirith binary (supports ~ expansion) # tirith_timeout: 5 # Scan timeout in seconds # tirith_fail_open: true # Allow commands if tirith unavailable # approval: # transport: builtin # Or an explicitly enabled plugin transport name # transport_fallback: deny # Set builtin to opt into fallback on transport failure # ============================================================================= # Browser Tool Configuration # ============================================================================= browser: # Inactivity timeout in seconds - browser sessions are automatically closed # after this period of no activity between agent loops (default: 120 = 2 minutes) inactivity_timeout: 120 # Let an authenticated browser extension register as the controller for an # existing Hermes session. Disabled by default. Local API registration also # requires the API server bearer key to be configured. extension_control: enabled: false # Maximum characters of snapshot content before truncate-and-store. # Increase for long pages (for example, documentation or financial reports), # or decrease to reduce context usage. Minimum: 1000; default: 15000 # (same per-page budget as web_extract). # snapshot_threshold: 15000 # ============================================================================= # Tool Loop Guardrails # ============================================================================= # Soft warnings are enabled by default. They append guidance to repeated failed # or non-progressing tool results but still let the tool execute. Hard stops are # opt-in circuit breakers for autonomous/cron sessions where stopping a loop is # preferable to spending the full iteration budget. 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 # ============================================================================= # Context Compression (Auto-shrinks long conversations) # ============================================================================= # When conversation approaches model's context limit, middle turns are # automatically summarized to free up space while preserving important context. # # HOW IT WORKS: # 1. Tracks actual token usage from API responses (not estimates) # 2. When prompt_tokens >= threshold% of model's context_length, triggers compression # 3. Protects first 3 turns (system prompt, initial request, first response) # 4. Protects last N turns (default 20 messages = ~10 full turns of recent context) # 5. Summarizes middle turns using a fast/cheap model # 6. Inserts summary as a user message, continues conversation seamlessly # # Post-compression tail budget is target_ratio × threshold × context_length: # 200K context, threshold 0.50, ratio 0.20 → 20K tokens of recent tail preserved # 1M context, threshold 0.50, ratio 0.20 → 100K tokens of recent tail preserved # compression: # Enable automatic context compression (default: true) # Set to false if you prefer to manage context manually or want errors on overflow enabled: true # Fail closed before lossy compaction unless an active memory provider that # implements the pre-compress checkpoint contract (API v2) confirms its # durable checkpoint (default: false). With this on and no checkpoint, the # compaction attempt errors with BLOCKED_MISSING_PREREQUISITE and the # uncompressed transcript is preserved for a later retry. The gate binds to # every compaction authority: server-side native compaction # (codex_responses_native) is suppressed while armed, post-turn # micro-compaction is forced off (no checkpoint hook in its path), and the # codex_app_server API mode is refused at agent init (the codex agent # compacts its own thread — no checkpoint can be guaranteed there). Only # enable it with a checkpoint-capable provider configured — see # website/docs/developer-guide/memory-provider-plugin.md # ("Pre-Compress Checkpoints"). checkpoint_required: false # Opt-in compression progress notices on chat platforms (default: false). # By design, routine automatic compression is SILENT on human-facing chat # gateways (Telegram, Discord, Slack, ...) — it happens in the background # with server-side logging only. Set true to also deliver the routine # progress statuses (compacting started, preflight/pre-API compression, # idle compaction, retry progress, and the compaction-complete notice) to # chat platforms. Unrelated operational noise (auxiliary model failures, # provider retry chatter) stays suppressed either way, and compression # FAILURE notices + manual /compress feedback are always visible # regardless of this setting. (#52995) progress_notices: false # Trigger compression at this % of model's context limit (default: 0.50 = 50%) # Lower values = more aggressive compression, higher values = compress later # 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 above 0.75 to override. threshold: 0.50 # Per-model threshold overrides: keys are substring-matched against the model # name (longest match wins). Useful when some models need different compaction # points — e.g. a 1M-context model can compress later (0.30) while a 128K # model needs to compress earlier (0.60). The small-context floor (75% for # <512K models) still applies on top of per-model overrides. # model_thresholds: # "glm-5.2": 0.40 # "claude-sonnet": 0.35 # "gpt-5": 0.30 # Optional absolute token cap for the compression trigger (default: null = disabled). # When set, compression fires at the LOWER of the ratio-based threshold and this # absolute token count — first-fires-wins. It never fires later than this count # regardless of which model is active (useful when switching between models with # very different context windows). Clamped to the model's context length at # apply-time, so a cap above the window is a no-op (ratio-based threshold wins). # Survives model switches and fallback activations. # threshold_tokens: 200000 # Existing Codex gpt-5.5 behavior: raise Hermes' compaction trigger to 85% # for the ChatGPT Codex OAuth route. Set false to opt back down to threshold. codex_gpt55_autoraise: true # Fraction of the threshold to preserve as recent tail (default: 0.20 = 20%) # e.g. 20% of 50% threshold = 10% of total context kept as recent messages. # Summary output is separately capped at 12K tokens (Gemini output limit). # Range: 0.10 - 0.80 target_ratio: 0.20 # Number of most-recent messages to always preserve (default: 20 ≈ 10 full turns) # Higher values keep more recent conversation intact at the cost of more aggressive # compression of older turns. protect_last_n: 20 # Minimum number of REAL (actionable) user messages guaranteed to survive in # the uncompressed tail (default: 1 = the existing single last-user anchor, # behavior-preserving). Raise to e.g. 3 to keep the last 3 real user turns # verbatim even when bulky tool outputs fill the tail token budget — blank # platform echoes, compaction handoffs, and synthetic continuation rows never # count toward N. The tail can exceed the token budget when this pulls the # cut back; the guarantee wins over the budget by design. min_tail_user_messages: 1 # Compression retry rounds before a turn gives up with "max compression # attempts reached" (default: 3, same as the previous hardcoded value). # Raise (e.g. 6) for tool-schema-heavy sessions where 3 rounds cannot bring # the request estimate under the threshold. Validated >= 1, hard cap 10. max_attempts: 3 # Codex app-server (codex CLI runtime) thread-compaction mode. The codex # agent owns the real thread context on this runtime, so Hermes' summarizer # cannot shrink it — compaction goes through the app server instead. # native = let Codex decide when to compact its own thread (default) # hermes = let Hermes threshold trigger Codex thread/compact/start # off = Hermes will not auto-trigger compaction; Codex may still compact natively codex_app_server_auto: native # Native OpenAI Responses server-side compaction (default: false). When true, # gpt-5.6-family models on the DIRECT OpenAI API (api.openai.com) or a ChatGPT # Codex subscription compact server-side: OpenAI prunes older context into an # encrypted checkpoint that Hermes replays on later turns. No other provider, # route, or model is affected. Hermes' local compression stays armed as the # fallback and still handles every non-eligible session. codex_responses_native: false # Server-side compaction trigger in input tokens. Clamped below the local # compression threshold at request time so the server compacts first. codex_responses_compact_threshold: 200000 # Number of non-system messages to protect at the head of the transcript, in # ADDITION to the system prompt (which is always implicitly protected). # Head messages are NEVER summarized — they survive every compression # indefinitely. This gives stable early context for short/medium sessions, # but in long-running sessions that rely on rolling compaction the pinned # opening turns may not match how you want the session framed over time. # Set to 0 to preserve ONLY the system prompt (plus the rolling summary # and recent tail) — the cleanest configuration for long-running sessions. # Default 3 preserves the system prompt plus the first three non-system # head messages, matching the pre-feature behaviour. protect_first_n: 3 # Idle compaction (default: 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 you come # back to later doesn't re-read its full stale context on every turn. # Time-based, so it complements (does not replace) the size-based `threshold` # above. It is skipped when the context is already small (at or below the # post-compression target = threshold × target_ratio), so it never wastes a # summarization on a short idle thread. Example: 1800 = compact after 30 min idle. idle_compact_after_seconds: 0 # Proactive tool-result prune (default: 0 = disabled). Opt-in token trigger # for a deterministic, no-LLM prune of OLD tool-result payloads, run # independently of `threshold` above. On large-window models (512K/1M) the # ratio threshold rarely fires, so bulky tool outputs (terminal dumps, file # reads, web extracts) ride along in history and get re-billed every turn. # When re-sent history exceeds this many tokens, the prune dedupes identical # results, summarizes older oversized ones, and truncates large tool-call # arguments — protecting the most recent `protect_last_n` messages and never # calling the model. Try 48000 to enable. Built-in compressor engine only; # other context engines inherit a safe no-op. # NOTE: a committed prune rewrites already-sent history, which invalidates # the provider's prompt-cache prefix — the min_reclaim gate below keeps # those cache breaks episodic (like a compression boundary) instead of # per-turn. proactive_prune_tokens: 0 # The prune's summarize pass only touches tool results larger than this many # characters (clamped to >= 200 so a generated summary can't be # re-summarized). Default 8000. proactive_prune_min_result_chars: 8000 # A proactive prune only COMMITS when it reclaims at least this many tokens # (measured on the pruned output). This is the prompt-cache hysteresis gate: # one meaningful, amortized cache break per batch of stale tool output # instead of a tiny break on every tool iteration. 0 = commit any non-zero # prune. Default 4096. proactive_prune_min_reclaim_tokens: 4096 # To pin a specific model/provider for compression summaries, use the # auxiliary section below (auxiliary.compression.provider / model). # ============================================================================= # Tool-result budget (optional) # ============================================================================= # Controls when a large tool result is spilled to disk (full output saved to # $HERMES_HOME/cache/spillover, preview + path kept in context). MCP tool # results (tools named mcp_*) spill at a tighter 50,000-char threshold than # the generic 100K default: MCP servers routinely return un-paginated 20-50K # payloads that bloat context and slow every subsequent turn. Nothing is # lost — the full result is on disk and readable with read_file. # # tool_budget: # mcp_result_size_chars: 50000 # per-result spillover threshold for mcp_* tools # ============================================================================= # Anthropic prompt caching TTL # ============================================================================= # When prompt caching is active (Claude via OpenRouter or native Anthropic), # Anthropic supports two TTL tiers for cached prefixes: "5m" (default) and # "1h". Other values are ignored and "5m" is used. # prompt_caching: cache_ttl: "5m" # use "1h" for long sessions with pauses between turns # ============================================================================= # Auxiliary Models (Advanced — Experimental) # ============================================================================= # Hermes uses lightweight "auxiliary" models for side tasks: image analysis, # browser screenshot analysis, web page summarization, TTS audio-tag insertion, # and context compression. # # By default these use Gemini Flash via OpenRouter or Nous Portal and are # auto-detected from your credentials. You do NOT need to change anything # here for normal usage. # # WARNING: Overriding these with providers other than OpenRouter or Nous Portal # is EXPERIMENTAL and may not work. Not all models/providers support vision, # produce usable summaries, or accept the same API format. Change at your own # risk — if things break, reset to "auto" / empty values. # # Each task has its own provider + model pair so you can mix providers. # For example: OpenRouter for vision (needs multimodal), but your main # local endpoint for compression (just needs text). # # Provider options: # "auto" - Best available: OpenRouter → Nous Portal → main endpoint (default) # "openrouter" - Force OpenRouter (requires OPENROUTER_API_KEY) # "nous" - Force Nous Portal (requires: hermes auth add nous) # "gemini" - Force Google AI Studio direct (requires: GOOGLE_API_KEY or GEMINI_API_KEY) # "ollama-cloud" - Ollama Cloud (requires: OLLAMA_API_KEY) # "codex" - Force Codex OAuth (requires: hermes model → Codex). # Uses gpt-5.3-codex which supports vision. # "main" - Use your custom endpoint (OPENAI_BASE_URL + OPENAI_API_KEY). # Works with OpenAI API, local models, or any OpenAI-compatible # endpoint. Also falls back to Codex OAuth and API-key providers. # # Model: leave empty to use the provider's default. When empty, OpenRouter # uses "google/gemini-3-flash-preview" and Nous uses "gemini-3-flash". # Other providers pick a sensible default automatically. # # auxiliary: # # Image analysis: vision_analyze tool + browser screenshots # vision: # provider: "auto" # model: "" # e.g. "google/gemini-2.5-flash", "openai/gpt-4o" # timeout: 30 # LLM API call timeout (seconds) # download_timeout: 30 # Image HTTP download timeout (seconds) # # Increase for slow connections or self-hosted image servers # reasoning_effort: "" # Per-task thinking level: none, minimal, low, medium, # # high, xhigh, max, ultra. Empty = provider default. # # Works on every auxiliary task block (vision, # # web_extract, compression, title_generation, curator, # # background_review, moa_reference, ...). Example: run # # compression at "low" and vision at "none" to cut # # side-task latency/cost on reasoning models. # # # Web page scraping / summarization + browser page text extraction # web_extract: # provider: "auto" # model: "" # reasoning_effort: "low" # # # Gemini 3.1 TTS hidden audio-tag insertion # tts_audio_tags: # provider: "auto" # empty model = your main chat model # model: "" # timeout: 30 # # # Automatic session title generation after the first exchange # title_generation: # enabled: true # set false to disable auto-title generation # provider: "auto" # model: "" # timeout: 30 # language: "" # empty = match the user's language; or e.g. "English" # # # Session search — summarizes matching past sessions # session_search: # provider: "auto" # model: "" # timeout: 30 # max_concurrency: 3 # Limit parallel summaries to reduce request-burst 429s # extra_body: {} # Provider-specific OpenAI-compatible request fields # # Example for providers that support request-body # # reasoning controls: # # extra_body: # # enable_thinking: false # # Some vLLM/Qwen deployments expect this nested: # # extra_body: # # chat_template_kwargs: # # enable_thinking: false # # # Auto-generated short session titles after the first exchange. # # Each active Discord/Telegram channel can spawn a background title # # call. Cap concurrency to keep retries during provider incidents # # from amplifying the request burst. Leave unset for legacy behavior # # (unlimited). # title_generation: # provider: "auto" # model: "" # # max_concurrency: 2 # Optional: cap simultaneous title calls # # # Context compression — summarizes long sessions to shrink the prompt. # # Heavy and often hits the slowest provider chain. Setting a small # # cap prevents many sessions from compressing simultaneously during # # provider degradation. Leave unset for legacy behavior (unlimited). # compression: # provider: "auto" # model: "" # # max_concurrency: 2 # Optional: cap simultaneous compression calls # # # Post-turn memory/skill self-improvement review fork. Runs after a turn # # when the nudge intervals fire; writes skills/memories in a daemon thread. # # Usage is recorded under session_model_usage task='background_review'. # background_review: # enabled: true # false = skip automatic forks (/refine still works) # provider: "auto" # or pin a cheaper model (see memory.md) # model: "" # ============================================================================= # Persistent Memory # ============================================================================= # Bounded curated memory injected into the system prompt every session. # Two stores: MEMORY.md (agent's notes) and USER.md (user profile). # Character limits keep the memory small and focused. The agent manages # pruning -- when at the limit, it must consolidate or replace entries. # Disabled by default in batch_runner. # memory: # Agent's personal notes: environment facts, conventions, things learned memory_enabled: true # User profile: preferences, communication style, expectations user_profile_enabled: true # Character limits (~2.75 chars per token, model-independent) memory_char_limit: 2200 # ~800 tokens user_char_limit: 1375 # ~500 tokens # Periodic memory nudge: remind the agent to consider saving memories # every N user turns. Set to 0 to disable. Only active when memory is enabled. nudge_interval: 10 # Nudge every 10 user turns (0 = disabled) # ============================================================================= # Session Reset Policy (Messaging Platforms) # ============================================================================= # Controls when messaging sessions (Telegram, Discord, WhatsApp, Slack) are # automatically cleared. Default is "none": sessions never auto-reset — # conversation context lives until you /reset or /new manually, or context # compression kicks in. Opt in to automatic resets if you prefer sessions to # clear on a schedule (long-lived context increases API cost per message, # though prompt caching and compression keep this manageable). # # When an automatic reset triggers, the agent first saves important # information to its persistent memory — but the conversation context is # wiped. The agent starts fresh but retains learned facts via its memory # system. # # Modes: # "none" - Never auto-reset (default); context lives until /reset or compression # "idle" - Reset after N minutes of inactivity # "daily" - Reset at a fixed hour each day # "both" - Reset on EITHER inactivity timeout or daily boundary # session_reset: mode: none # "none", "idle", "daily", or "both" idle_minutes: 1440 # Inactivity timeout in minutes (used by "idle"/"both") at_hour: 4 # Daily reset hour, 0-23 local time (used by "daily"/"both") # Maximum number of simultaneously active chat sessions across CLI, TUI, # dashboard chat, and messaging gateway. Set to null, 0, or omit to allow # unlimited concurrent sessions. When the limit is reached, new sessions get a # clean error while existing active sessions keep their normal behavior. This # top-level key takes precedence over gateway.max_concurrent_sessions. The cap # is a best-effort single-host/profile runtime guard; Hermes fails open if the # local runtime lease registry cannot be read or locked. max_concurrent_sessions: null # When true, group/channel chats use one session per participant when the platform # provides a user ID. This is the secure default and prevents users in the same # room from sharing context, interrupts, and token costs. Set false only if you # explicitly want one shared "room brain" per group/channel. group_sessions_per_user: true # Startup sweep of session rows orphaned by a dead gateway process. # The normal disconnect cleanup runs on an in-process grace timer, so a # gateway restart (update, crash, systemd) leaves those rows permanently # "active". On every gateway boot — stdio TUI *and* the desktop/dashboard # WS sidecar — tui/desktop/subagent rows whose start time AND newest # message are both older than the session TTL (HERMES_TUI_SESSION_TTL_S, # default 6h) are closed with end_reason "startup_orphan_reap". # Messaging-platform sessions (Telegram, Discord, ...) are never touched; # live in-memory sessions are excluded; swept sessions stay resumable. # # dashboard: # startup_orphan_sweep: true # ───────────────────────────────────────────────────────────────────────────── # API Server — per-client model routing # ───────────────────────────────────────────────────────────────────────────── # Route different API clients to different models/providers on a single # Hermes deployment. Clients choose a backend by sending a specific string # as the OpenAI ``model`` field. Unmapped model values fall back to the # global model configured in the ``model:`` section above, and an explicit # session /model override always wins over a route. # # Configure via the ``platforms.api_server.extra.model_routes`` gateway # config block: # # platforms: # api_server: # enabled: true # extra: # key: "your-api-server-secret" # model_routes: # # Xiaozhi clients send model="minimax-m2" → routed to MiniMax via OpenRouter # minimax-m2: # model: "minimax/minimax-m1" # provider: "openrouter" # optional — overrides global provider # # api_key: "sk-..." # optional — per-route UPSTREAM provider # # key (NOT caller auth; never logged) # # base_url: "https://..." # optional — per-route base URL # # GPT clients keep their own alias # gpt-5: # model: "openai/gpt-5" # provider: "openrouter" # # Configured aliases are automatically listed by GET /v1/models so clients # can discover them without manual coordination. Caller authentication is # unchanged: every request still authenticates with the global API server # key (``extra.key`` / API_SERVER_KEY). # ───────────────────────────────────────────────────────────────────────────── # Gateway Streaming # ───────────────────────────────────────────────────────────────────────────── # Stream tokens to messaging platforms in real-time. The bot sends a message # on first token, then progressively edits it as more tokens arrive. # Disabled by default — enable to try the streaming UX on Telegram/Discord/Slack. # For Telegram, partial edits are sent as plain text and only the final edit uses MarkdownV2. streaming: enabled: false # transport: edit # "edit" = progressive editMessageText # edit_interval: 0.3 # seconds between message edits # buffer_threshold: 40 # chars before forcing an edit flush # cursor: " ▉" # cursor shown during streaming # ============================================================================= # Skills Configuration # ============================================================================= # Skills are reusable procedures the agent can load and follow. The agent can # also create new skills after completing complex tasks. # skills: # Nudge the agent to create skills after complex tasks. # Every N tool-calling iterations, remind the model to consider saving a skill. # Set to 0 to disable. creation_nudge_interval: 15 # External skill directories — share skills across tools/agents without # copying them into ~/.hermes/skills/. Each path is expanded (~ and ${VAR}) # and resolved to an absolute path. External dirs are read-only: skill # creation always writes to ~/.hermes/skills/. Local skills take precedence # when names collide. # external_dirs: # - ~/.agents/skills # - /home/shared/team-skills # ============================================================================= # Agent Behavior # ============================================================================= agent: # Maximum tool-calling iterations per conversation (default: 500) # Higher = more room for complex tasks, but costs more tokens # Recommended: 20-30 for focused tasks, 50-100 for open exploration max_turns: 500 # Inactivity timeout for gateway agent runs (seconds, 0 = unlimited). # The agent can run indefinitely when actively calling tools or receiving # API responses. Only fires after the agent has been idle for this duration. # gateway_timeout: 1800 # Maximum time an alias routing key waits for an active turn holding the same # resolved session lease. On expiry Hermes rejects this inbound message and # asks the user to resend rather than running it without serialization. # Non-positive values fall back to the 1800-second default. # gateway_turn_lease_timeout: 1800 # Staged warning: send a warning before escalating to full timeout. # Fires once per run when inactivity reaches this threshold (seconds). # Set to 0 to disable the warning. # gateway_timeout_warning: 900 # Session stall watchdog (seconds). When a busy session has a pending # inbound follow-up and the agent activity clock is idle this long, the # gateway logs a WARNING and notifies the user to try /new. Does not kill # the turn (see gateway_timeout). 0 = disable. Default 300. # session_stall_timeout: 300 # Related in-agent compression timeouts (they live under the top-level # compression: block, shown here for discoverability next to the stall # watchdog they complement — a hung compression is a common stall cause): # compression: # context_timeout_seconds: 120 # inactivity budget for in-agent # # compress_context (0 = disable) # context_total_ceiling_seconds: 600 # absolute cap on the pre-commit # # wait even while tokens stream # Graceful drain timeout for gateway stop/restart (seconds). # Default 0 = no drain: a restart interrupts in-flight agents immediately, # cleans up, and exits. Set a positive value only if you want a grace # window on /restart, and keep it well under systemd's TimeoutStopSec. # restart_drain_timeout: 0 # Cron-only floor under the same drain (seconds). Default 30. # restart_drain_timeout above is written for chat turns, which are cheap to # interrupt: the user is told the gateway is restarting and the session # resumes on their next message. A cron run has no such safety net — it is # recorded in jobs.json as a permanent failure, nobody is waiting on it, and # a recurring job simply skips to its next schedule. So in-flight cron work # gets its own grace window instead of inheriting the 0 above. # Clamped at runtime to the shutdown-watchdog leash (restart_drain_timeout # + 60s) minus teardown headroom, so values past ~50s need a matching # TimeoutStopSec bump to take effect. Set 0 to opt out and drain cron on # restart_drain_timeout like before. # cron_drain_timeout: 30 # Upper bound (seconds) a submitted prompt waits for the deferred agent # build (MCP discovery, model metadata, skills scan) before failing with a # visible error. The wait is patient — the message is delivered as soon as # the build completes, and a progress notice is shown past 30s — so this cap # only fires on a genuinely hung build. Raise it for deployments with many # slow or unreachable MCP servers. Default 600. # build_wait_timeout: 600 # Max app-level retry attempts for API errors (connection drops, provider # timeouts, 5xx, etc.) before the agent surfaces the failure. Lower this # to 1 if you use fallback providers and want fast failover on flaky # primaries (default 3). The OpenAI SDK does its own low-level retries # underneath this wrapper — this is the Hermes-level loop. # api_max_retries: 3 # After the agent edits code without fresh passing verification, nudge it to # verify before finishing. The default "auto" enables it on interactive # coding surfaces (CLI, TUI, desktop) and programmatic callers, and disables # it on conversational messaging surfaces (Telegram, Discord, etc.) where the # verification summary would reach a human as chat noise. Set true or false to # force it on or off; the HERMES_VERIFY_ON_STOP env var (1/0) takes precedence. # verify_on_stop: auto # Standing operator instructions for the coding posture (when Hermes is in a # code workspace). Appended to the coding brief as an extra system block, so # you can pin project-wide workflow rules without editing the shipped brief. # Accepts a string or a list of strings. Takes effect next session. # coding_instructions: # - "For UI work, don't run tsc/lint until I approve the look." # - "Clean the diff before you commit and push." # 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 that nudge terse. # verify_guidance: true # A `pre_verify` hook (plugin or shell, see Event Hooks docs) can keep the # agent going one more turn to verify/clean before finishing. This caps how # many times one turn may be nudged to continue, so a hook can't trap the loop. # Default 3. # max_verify_nudges: 3 # Enable verbose logging verbose: false # Reasoning effort level (OpenRouter and Nous Portal) # Controls how much "thinking" the model does before responding. # Options: "xhigh" (max), "high", "medium", "low", "minimal", "none" (disable) reasoning_effort: "medium" # Per-model reasoning effort overrides (optional dict) # Key: any sensible model spelling works (exact, dots↔dashes interchangeable, # provider prefix optional). First match wins. # Value: reasoning effort level (same options as reasoning_effort) # Override the global reasoning_effort for that specific model. # NOTE: no `hermes config set` support for this key -- edit YAML directly. # reasoning_overrides: # "openrouter/anthropic/claude-opus-4.5": "xhigh" # "openai/gpt-5": "low" # "claude-opus-4.6": "high" # bare model name also works # "deepseek/deepseek-v4-pro": "xhigh" # dots and dashes are interchangeable reasoning_overrides: {} # Custom personalities (use with /personality command). # Built-ins (helpful, concise, technical, creative, teacher, kawaii, catgirl, # pirate, shakespeare, surfer, noir, uwu, philosopher, hype) are always # available on every surface — defined once in hermes_cli/personality.py. # Entries here ADD new personalities or OVERRIDE a built-in by name. # The active selection is stored in display.personality (never here, and # never in agent.system_prompt — that field is your manual system prompt). personalities: {} # mentor: "You are a supportive mentor. Guide, don't lecture." # reviewer: # system_prompt: "You are a meticulous code reviewer." # tone: "direct" # style: "terse" # ============================================================================= # Toolsets # ============================================================================= # Control which tools the agent has access to. # Use `hermes tools` to interactively enable/disable tools per platform. # ============================================================================= # Platform Toolsets (per-platform tool configuration) # ============================================================================= # Override which toolsets are available on each platform. # If a platform isn't listed here, its built-in default is used. # # You can use EITHER: # - A preset like "hermes-cli" or "hermes-telegram" (curated tool set) # - A list of individual toolsets to compose your own (see list below) # # Supported platform keys: cli, telegram, discord, whatsapp, slack, qqbot, teams, google_chat # # Examples: # # # Use presets (same as defaults): # platform_toolsets: # cli: [hermes-cli] # telegram: [hermes-telegram] # # # Custom: give Telegram only web + terminal + file + planning: # platform_toolsets: # telegram: [web, terminal, file, todo] # # # Custom: CLI without browser or image gen: # platform_toolsets: # cli: [web, terminal, file, skills, todo, tts, cronjob] # # # Restrictive: Discord gets read-only tools only: # platform_toolsets: # discord: [web, vision, skills, todo] # # If not set, defaults are: # cli: hermes-cli (everything + cronjob management) # telegram: hermes-telegram (terminal, file, web, vision, image, tts, browser, skills, todo, cronjob, messaging) # discord: hermes-discord (same as telegram) # whatsapp: hermes-whatsapp (same as telegram) # slack: hermes-slack (same as telegram) # signal: hermes-signal (same as telegram) # homeassistant: hermes-homeassistant (same as telegram) # qqbot: hermes-qqbot (same as telegram) # teams: hermes-teams (same as telegram) # google_chat: hermes-google_chat (same as telegram) # platform_toolsets: cli: [hermes-cli] telegram: [hermes-telegram] discord: [hermes-discord] whatsapp: [hermes-whatsapp] slack: [hermes-slack] signal: [hermes-signal] homeassistant: [hermes-homeassistant] qqbot: [hermes-qqbot] yuanbao: [hermes-yuanbao] teams: [hermes-teams] google_chat: [hermes-google_chat] # ============================================================================= # Gateway Platform Settings # ============================================================================= # Optional per-platform messaging settings. # Platform-specific knobs live under `extra`. # # platforms: # telegram: # reply_to_mode: "first" # off | first | all # # guest_mode lets explicit @mentions from non-allowlisted groups through. # # Default false; ordinary messages, replies, and regex wake words stay blocked. # guest_mode: false # # allowed_chats: ["-1001234567890"] # extra: # disable_link_previews: false # Set true to suppress Telegram URL previews in bot messages # rich_messages: false # Bot API 10.1 rich messages (tables/task lists/details/math); default false for copyable legacy MarkdownV2, set true to opt in # rich_drafts: false # Experimental rich draft previews during Telegram DM streaming; default false because Telegram Desktop/macOS can visually overlay draft frames # command_menu: # # Telegram allows up to 100 BotCommands; Hermes defaults to 60 so # # all built-in commands plus common skill commands stay visible # # while remaining under Telegram's payload-size limit. Clamped 1..100. # max_commands: 60 # # prepend = user priority first, then Hermes defaults # # append = Hermes defaults first, then user priority # # replace = only the list below defines priority # priority_mode: prepend # priority: # - my_plugin_command # slack: # extra: # # Render live tool calls as Slack-native plan/task cards. This explicit # # opt-in works even though Slack text tool_progress defaults to off. # native_task_cards: false # # Suppress automatic link-preview cards without removing clickable links. # # Omit either key to preserve Slack's default for that preview type. # unfurl_links: false # unfurl_media: false # webhook: # extra: # # Route scripts default to a 30 second timeout. Scripts must live under # # the active profile's scripts directory and receive webhook JSON on stdin. # script_timeout_seconds: 30 # # Discord-specific settings (config.yaml top-level, not under platforms:): # # discord: # require_mention: true # Require @mention in server channels (default: true) # auto_thread: true # Auto-create thread on @mention (default: true) # free_response_channels: "" # Channel IDs where no mention is needed # reactions: true # Show processing reactions (default: true) # history_backfill: true # Recover missed channel messages on mention (default: true) # history_backfill_limit: 50 # Max messages to scan backwards (default: 50) # ───────────────────────────────────────────────────────────────────────────── # Available toolsets (use these names in platform_toolsets or the toolsets list) # # Run `hermes chat --list-toolsets` to see all toolsets and their tools. # Run `hermes chat --list-tools` to see every individual tool with descriptions. # ───────────────────────────────────────────────────────────────────────────── # # INDIVIDUAL TOOLSETS (compose your own): # web - web_search, web_extract # search - web_search only (no scraping) # terminal - terminal, process # file - read_file, write_file, patch, search # browser - browser_navigate, browser_snapshot, browser_click, browser_type, # browser_scroll, browser_back, browser_press, # browser_get_images, browser_vision (requires BROWSERBASE_API_KEY) # vision - vision_analyze (requires OPENROUTER_API_KEY) # image_gen - image_generate (requires FAL_KEY) # skills - skills_list, skill_view # skills_hub - skill_hub (search/install/manage from online registries — user-driven only) # todo - todo (in-memory task planning, no deps) # tts - text_to_speech (Edge TTS free, or ELEVENLABS/OPENAI/MINIMAX/MISTRAL key) # cronjob - cronjob (create/list/update/pause/resume/run/remove scheduled tasks) # # PRESETS (curated bundles): # hermes-cli - All of the above except send_message # hermes-telegram - terminal, file, web, vision, image_gen, tts, browser, # skills, todo, cronjob, send_message # hermes-discord - Same as hermes-telegram # hermes-whatsapp - Same as hermes-telegram # hermes-slack - Same as hermes-telegram # # COMPOSITE: # debugging - terminal + web + file # safe - web + vision (no terminal access) # all - Everything available # # web - Web search and content extraction (web_search, web_extract) # search - Web search only, no scraping (web_search) # terminal - Command execution and process management (terminal, process) # file - File operations: read, write, patch, search # browser - Full browser automation (navigate, click, type, screenshot, etc.) # vision - Image analysis (vision_analyze) # image_gen - Image generation with FLUX (image_generate) # skills - Load skill documents (skills_list, skill_view) # todo - Task planning and tracking for multi-step work # memory - Persistent memory across sessions (personal notes + user profile) # session_search - Search and recall past conversations (FTS5 + Gemini Flash summarization) # tts - Text-to-speech (Edge TTS free, ElevenLabs, OpenAI, MiniMax, Mistral) # cronjob - Schedule and manage automated tasks (CLI-only) # # Composite toolsets: # debugging - terminal + web + file (for troubleshooting) # safe - web + vision (no terminal access) # NOTE: The top-level "toolsets" key is deprecated and ignored. # Tool configuration is managed per-platform via platform_toolsets above. # Use `hermes tools` to configure interactively, or edit platform_toolsets directly. # # CLI override: hermes chat --toolsets terminal,web,file # ============================================================================= # MCP (Model Context Protocol) Servers # ============================================================================= # Connect to external MCP servers to add tools from the MCP ecosystem. # Each server's tools are automatically discovered and registered. # See website/docs/user-guide/features/mcp.md for full documentation. # # Stdio servers (spawn a subprocess): # command: the executable to run # args: command-line arguments # env: environment variables (only these + safe defaults passed to subprocess) # # HTTP servers (connect to a URL): # url: the MCP server endpoint # headers: HTTP headers (e.g., for authentication) # # Optional per-server settings: # timeout: tool call timeout in seconds (default: 120) # connect_timeout: initial connection timeout (default: 60) # keepalive_interval: liveness ping cadence in seconds (default: 180). # Lower it below the server's session TTL for servers that expire idle # sessions quickly (e.g. Unreal Engine editor MCP, ~15s), otherwise idle # tool calls hit an expired session and pay a slow reconnect. Floored at 5s. # # mcp_servers: # time: # command: uvx # args: ["mcp-server-time"] # filesystem: # command: npx # args: ["-y", "@modelcontextprotocol/server-filesystem", "/home/user"] # notion: # url: https://mcp.notion.com/mcp # github: # command: npx # args: ["-y", "@modelcontextprotocol/server-github"] # env: # GITHUB_PERSONAL_ACCESS_TOKEN: "ghp_..." # # Sampling (server-initiated LLM requests) — enabled by default. # Per-server config under the 'sampling' key: # analysis: # command: npx # args: ["-y", "analysis-server"] # sampling: # enabled: true # default: true # model: "gemini-3-flash" # override model (optional) # max_tokens_cap: 4096 # max tokens per request # timeout: 30 # LLM call timeout (seconds) # max_rpm: 10 # max requests per minute # allowed_models: [] # model whitelist (empty = all) # max_tool_rounds: 5 # tool loop limit (0 = disable) # log_level: "info" # audit verbosity # ============================================================================= # Text-to-Speech # ============================================================================= # TTS defaults to Edge TTS unless changed in ~/.hermes/config.yaml. # Gemini TTS supports persona/director prompt files, and Gemini 3.1 Flash TTS # can use a hidden auxiliary rewrite pass to insert expressive square-bracket # audio tags into the TTS script without showing tags in chat. # # tts: # provider: "gemini" # speed: 1.0 # global speed multiplier (provider-specific overrides this) # gemini: # model: "gemini-3.1-flash-tts-preview" # voice: "Kore" # audio_tags: false # persona_prompt_file: "" # e.g. ~/.hermes/tts/radio-host.md # xai: # voice_id: "eve" # built-in or custom voice ID from xAI Console # language: "en" # BCP-47 code ("en", "pt-BR") or "auto" # speed: 1.0 # 0.7-1.5 playback speed # auto_speech_tags: false # insert expressive audio tags via LLM rewrite # text_normalization: false # normalize numbers/abbreviations/symbols # optimize_streaming_latency: 0 # 0-2, trades quality for lower latency # sample_rate: 24000 # 22050 / 24000 / 44100 / 48000 # bit_rate: 128000 # MP3 bitrate (codec=mp3 only) # ============================================================================= # Voice Transcription (Speech-to-Text) # ============================================================================= # Automatically transcribe voice messages on messaging platforms. # Providers: local (free, faster-whisper) | groq (free tier) | openai (Whisper API) | mistral (Voxtral Transcribe) # Set the corresponding API key in .env: GROQ_API_KEY, OPENAI_API_KEY, or MISTRAL_API_KEY. stt: enabled: true # provider: "local" # auto-detected if omitted # --- Cloud pre-upload silence trim (groq/openai/mistral/xai/elevenlabs/deepinfra) --- # Local whisper gets Silero VAD; cloud endpoints otherwise receive raw audio — # silence inflates upload time, per-audio-minute billing, and hallucination risk. # Collapses pauses with ffmpeg client-side; clips under 12s skip the trim, and # on any failure the original uploads untouched. # cloud_trim_silence: true # set false to always upload the original audio # cloud_trim_threshold_db: -40 # audio quieter than this counts as silence # cloud_trim_keep_ms: 300 # how much of each pause survives (keeps natural pacing) # Optional static transcription prompt: vocabulary/context hints for # prompt-capable backends. Composition is deterministic: this config value # is the base, then `pre_transcription` hooks run in registration order with # last-writer-wins semantics per field. # # Privacy: the final prompt is sent to the configured STT provider alongside # the audio. Do not include secrets or session context you would not send to # that provider. # # Length / truncation contract: Whisper-family backends (local, openai, # groq, deepinfra) only use the final ~224 prompt tokens, so Hermes # truncates longer prompts client-side (keeping the tail) with a WARNING # log — never an error. Other backends own their own validation. # # Provider behavior: # local (faster-whisper) -> `initial_prompt`, forwarded unchanged # openai / groq -> `prompt`, forwarded unchanged # mistral -> `prompt`, forwarded unchanged # deepinfra -> OpenAI-compatible `prompt`, unchanged # local_command/xai/ # elevenlabs -> unsupported; DEBUG log, then continue # plugin providers -> `prompt` in `**extra`; plugin owns limits # prompt: "Hermes, Teknium, Nous Research, kanban" local: model: "base" # tiny | base | small | medium | large-v3 | turbo # language: "" # auto-detect; set to "en", "es", "fr", etc. to force # initial_prompt: "" # Optional faster-whisper prompt, e.g. bias Chinese output to simplified Chinese # --- Anti-hallucination hardening (whisper decodes junk from silence without these) --- # vad: true # Silero VAD filter (default on) — silence never reaches whisper. # # Set false to restore raw behavior (e.g. transcribing music/ambient audio). # vad_min_silence_ms: 500 # min silence (ms) that splits speech chunks when vad is on # no_speech_prob_threshold: 0.6 # drop a segment only if no_speech_prob > this... # logprob_threshold: -1.0 # ...AND avg_logprob < this (both must hit — quiet real speech survives) # unload_after_idle_seconds: 0 # 0=never unload (default); e.g. 300 releases the model after 5min idle (frees VRAM on GPU; next voice message reloads it) language: "en" # GLOBAL language hint for every STT provider (per-provider language wins). Set "" for auto-detect. # groq: # model: "whisper-large-v3-turbo" # language: "" # blank = stt.language > HERMES_LOCAL_STT_LANGUAGE > auto-detect openai: model: "whisper-1" # whisper-1 | gpt-4o-mini-transcribe | gpt-4o-transcribe | gpt-transcribe language: "" # auto-detect; set to "en", "es", "fr", etc. to force # mistral: # model: "voxtral-mini-latest" # voxtral-mini-latest | voxtral-mini-2602 # deepinfra: # # Model id is discovered live from the DeepInfra catalog filtered # # by the `stt` surface tag — leave `model` blank to take the first # # live result. Pin only when you need a specific Whisper variant. # model: "" # Text-to-speech. Only the deepinfra block is documented here — the # remaining providers (edge, openai, xai, minimax, mistral, gemini, # elevenlabs, neutts, kittentts, piper) inherit sensible defaults from # DEFAULT_CONFIG in hermes_cli/config.py. # tts: # provider: "deepinfra" # deepinfra: # # Model id is discovered live from the DeepInfra catalog filtered # # by the `tts` surface tag — leave `model` blank to take the first # # live result. # model: "" # voice: "default" # "Hey Hermes" hands-free wake word (off by default; toggle with /wake). # Full engine/provider options inherit from DEFAULT_CONFIG in # hermes_cli/config_defaults.py — only capture placement is documented here. # wake_word: # enabled: false # capture: "auto" # auto | local | client # # auto: backend PortAudio mic when one exists; a remote # # desktop on a mic-less (headless/VPS) backend # # streams its own mic via the wake.feed RPC. # # local: always the backend mic (wake_word.input_device). # # client: always desktop-streamed PCM (detection stays # # on the backend). # Image generation. Each provider plugin reads its own ``image_gen.`` # block; deepinfra discovers models live from # api.deepinfra.com/v1/openai/models filtered by the ``image-gen`` tag — # no model id is hardcoded, so retired models disappear automatically. # image_gen: # provider: "deepinfra" # deepinfra: # # Leave `model` blank for the first live `image-gen`-tagged result. # model: "" # ============================================================================= # Response Pacing (Messaging Platforms) # ============================================================================= # Add human-like delays between message chunks. # human_delay: # mode: "off" # "off" | "natural" | "custom" # min_ms: 800 # Min delay (custom mode only) # max_ms: 2500 # Max delay (custom mode only) # ============================================================================= # Session Logging # ============================================================================= # Session trajectories are automatically saved to logs/ directory. # Each session creates: logs/session_YYYYMMDD_HHMMSS_UUID.json # # The session ID is displayed in the welcome banner for easy reference. # Logs contain full conversation history in trajectory format: # - System prompt, user messages, assistant responses # - Tool calls with inputs/outputs # - Timestamps for debugging # # No configuration needed - logging is always enabled. # To disable, you would need to modify the source code. # ============================================================================= # Code Execution Sandbox (Programmatic Tool Calling) # ============================================================================= # The execute_code tool runs Python scripts that call Hermes tools via RPC. # Intermediate tool results stay out of the LLM's context window. code_execution: timeout: 300 # Max seconds per script before kill (default: 300 = 5 min) max_tool_calls: 50 # Max RPC tool calls per execution (default: 50) # Local execution uses a persistent session kernel: variables, imports, and # loaded data survive across execute_code calls in one conversation, so # multi-step data work stops re-loading its inputs every call. Pass # reset=true on a call to discard state; a timed-out/interrupted cell kills # the kernel (next call starts fresh). Remote terminal backends currently # run per-call (remote kernel host is tracked follow-up work). Security # scrubbing, tool whitelist, and output redaction are identical either way. # kernel_idle_timeout: 1800 # Reap kernels idle longer than this (seconds) # max_session_kernels: 4 # Process-wide LRU cap on live kernels # ============================================================================= # Subagent Delegation # ============================================================================= # The delegate_task tool spawns child agents with isolated context. # Supports single tasks and batch mode (default 3 parallel, configurable). delegation: max_iterations: 250 # Max tool-calling turns per child (default: 250) # max_concurrent_children: 10 # Max parallel child agents per batch (default: 10, floor: 1, no ceiling). # WARNING: values above 10 multiply API cost linearly. # max_spawn_depth: 1 # Delegation tree depth cap (range: 1-3, default: 1 = flat). # Raise to 2 to allow workers to spawn their own subagents. # Requires role="orchestrator" on intermediate agents. # orchestrator_enabled: true # Kill switch for role="orchestrator" children (default: true). # subagent_auto_approve: false # When a subagent hits a dangerous-command approval prompt, auto-deny (default: false) # or auto-approve "once" (true) instead of blocking on stdin. # The parent TUI owns stdin, so blocking would deadlock; non-interactive resolution is required. # Both choices emit a logger.warning audit line. Flip to true only for cron/batch pipelines. # inherit_mcp_toolsets: true # When explicit child toolsets are narrowed, also keep the parent's MCP toolsets (default: true). Set false for strict intersection. # model: "google/gemini-3-flash-preview" # Override model for subagents (empty = inherit parent) # provider: "openrouter" # Override provider for subagents (empty = inherit parent) # # Resolves full credentials (base_url, api_key) automatically. # # Supported: openrouter, nous, zai, kimi-coding, minimax # # Cost tip: keep the parent on a frontier model and pin # # delegation.model to an inexpensive one — children carry the # # vast majority of tokens, so this is where spend is cut while # # planning quality stays with the frontier parent. # ============================================================================= # Honcho Integration (Cross-Session User Modeling) # ============================================================================= # AI-native persistent memory via Honcho (https://honcho.dev/). # Builds a deeper understanding of the user across sessions and tools. # Runs alongside USER.md — additive, not a replacement. # # Requires: pip install honcho-ai # Config: ~/.honcho/config.json (shared with Claude Code, Cursor, etc.) # API key: HONCHO_API_KEY in ~/.hermes/.env or ~/.honcho/config.json # # Hermes-specific overrides (optional — most config comes from ~/.honcho/config.json): # honcho: {} # ============================================================================= # Display # ============================================================================= display: # Use compact banner mode (hides the ASCII-art banner, shows a single line). # true: Compact single-line banner # false: Full ASCII banner with tool/skill summary (default) compact: false # Tool progress display level (CLI and gateway) # off: Silent — no tool activity shown, just the final response # new: Show a tool indicator only when the tool changes (skip repeats) # all: Show every tool call with a short preview (default) # verbose: Full args, results, and debug logs (same as /verbose) # log: Silent in chat; append every tool call to ~/.hermes/logs/tool_calls.log (gateway only) # Toggle at runtime with /verbose in the CLI tool_progress: all # Per-platform defaults can be quieter than the global setting. Telegram # tunes for mobile: tool_progress and busy_ack_detail default off (no # per-tool breadcrumb stream, no "iteration 21/60" debug detail in busy # acks or heartbeats), but interim_assistant_messages and # long_running_notifications STAY ON so the user has real signal between # turn start and final answer (mid-turn assistant commentary + a single # edit-in-place "⏳ Working — N min" heartbeat). Override under # display.platforms.telegram. # Auto-cleanup of temporary progress bubbles after the final response lands. # On platforms that support message deletion (currently Telegram), this # removes the tool-progress bubble, "⏳ Still working..." notices, and # context-pressure status messages once the final reply has been delivered — # keeping long-running turns visible live, then tidy afterward. Failed runs # leave the bubbles in place as breadcrumbs. Off by default. # Per-platform override: display.platforms.telegram.cleanup_progress # true: Delete tracked progress/status bubbles on successful turn # false: Leave everything in place (default) # Example: # display: # platforms: # telegram: # cleanup_progress: true cleanup_progress: false # Natural mid-turn assistant updates. # On gateway platforms, when true, completed assistant status messages are # sent as separate chat messages. On the Desktop app, when true, mid-turn # assistant narration streamed between tool calls is kept in the transcript # instead of the bubble collapsing to only the final message on completion. # Independent of tool_progress and gateway streaming. # true: Keep/send mid-turn assistant updates (default) # false: Only keep/send the final response interim_assistant_messages: true # Gateway-only long-running status heartbeats. # When false, the platform does not receive periodic "⏳ Working — N min" # notifications even if agent.gateway_notify_interval is non-zero. The # heartbeat edits a single message in place (where the adapter supports # editing) instead of posting a new bubble each interval. # Default: true everywhere, including Telegram (silent agents are worse # than a single edit-in-place heartbeat). long_running_notifications: true # Include detailed iteration/tool/status context in busy acknowledgments # and long-running heartbeats. When true, busy acks show "iteration 21/60, # terminal, 10 min" and the heartbeat shows "⏳ Working — 12 min, # iteration 21/60, terminal". When false (Telegram default), both stay # terse: "Interrupting current task" and "⏳ Working — 12 min, terminal". busy_ack_detail: true # What Enter does when Hermes is already busy (CLI and gateway platforms). # interrupt: Interrupt the current run and redirect Hermes (default) # queue: Queue your message for the next turn # steer: Inject your message mid-run via /steer, arriving at the agent # after the next tool call — no interrupt, no role violation. # Falls back to 'queue' if the agent isn't running yet or if # images are attached (steer only carries text). # Ctrl+C (or /stop in gateway) always interrupts regardless of this setting. # Toggle at runtime with /busy . busy_input_mode: interrupt # Background process notifications (gateway/messaging only). # Controls how chatty the process watcher is when you use # terminal(background=true, notify_on_complete=true) from Telegram/Discord/etc. # concise: One-line status message on completion; failures include a short # output tail (default) # off: No watcher messages at all # result: Only the final raw-output completion message # error: Only the final raw-output message when exit code != 0 # all: Running output updates + final raw-output message background_process_notifications: concise # Play terminal bell when agent finishes a response. # Useful for long-running tasks — your terminal will ding when the agent is done. # Works over SSH. Most terminals can be configured to flash the taskbar or play a sound. # true: Ring the terminal bell on each response # false: Silent (default) bell_on_complete: false # Show model reasoning/thinking before each response. # When enabled, a dim box shows the model's thought process above the response. # Toggle at runtime with /reasoning show or /reasoning hide. # true: Show the reasoning box # false: Hide reasoning (default) show_reasoning: false # Stream tokens to the terminal as they arrive instead of waiting for the # full response. The response box opens on first token and text appears # line-by-line. Tool calls are still captured silently. # true: Stream tokens as they arrive (default) # false: Wait for the full response before rendering streaming: true # Show [HH:MM] timestamps on user input and assistant response labels. # timestamps: false # ─────────────────────────────────────────────────────────────────────────── # Skin / Theme # ─────────────────────────────────────────────────────────────────────────── # Customize CLI visual appearance — banner colors, spinner faces, tool prefix, # response box label, and branding text. Change at runtime with /skin . # # Built-in skins: # default — Classic Hermes gold/kawaii # ares — Crimson/bronze war-god theme with spinner wings # mono — Clean grayscale monochrome # slate — Cool blue developer-focused # daylight — Bright light-mode theme # warm-lightmode — Warm paper-tone light-mode theme # poseidon — Sea-green/teal Olympian theme # sisyphus — Earthy stone-and-moss theme # charizard — Fiery orange dragon theme # # Custom skins: drop a YAML file in ~/.hermes/skins/.yaml # Schema (all fields optional, missing values inherit from default): # # name: my-theme # description: Short description # colors: # banner_border: "#HEX" # Panel border # banner_title: "#HEX" # Panel title # banner_accent: "#HEX" # Section headers (Available Tools, etc.) # banner_dim: "#HEX" # Dim/muted text # banner_text: "#HEX" # Body text (tool names, skill names) # ui_accent: "#HEX" # UI accent color # response_border: "#HEX" # Response box border color # spinner: # waiting_faces: ["(⚔)", "(⛨)"] # Faces shown while waiting # thinking_faces: ["(⚔)", "(⌁)"] # Faces shown while thinking # thinking_verbs: ["forging", "plotting"] # Verbs for spinner messages # wings: # Optional left/right spinner decorations # - ["⟪⚔", "⚔⟫"] # - ["⟪▲", "▲⟫"] # branding: # agent_name: "My Agent" # Banner title and branding # welcome: "Welcome message" # Shown at CLI startup # response_label: " ⚔ Agent " # Response box header label # prompt_symbol: "⚔" # Prompt symbol (bare token; renderers add trailing space) # tool_prefix: "╎" # Tool output line prefix (default: ┊) # skin: default # ============================================================================= # Model Aliases — short names for /model command # ============================================================================= # Map short aliases to exact (model, provider, base_url) tuples. # Used by /model tab completion and resolve_alias(). # Aliases are checked BEFORE the models.dev catalog, so they can route # to endpoints not in the catalog (e.g. Ollama Cloud, local servers). # # model_aliases: # opus: # model: claude-opus-4-6 # provider: anthropic # qwen: # model: "qwen3.5:397b" # provider: custom # base_url: "https://ollama.com/v1" # glm: # model: glm-4.7 # provider: custom # base_url: "https://ollama.com/v1" # ============================================================================= # Privacy # ============================================================================= # privacy: # # Redact PII from the LLM context prompt. # # When true, phone numbers are stripped and user/chat IDs are replaced # # with deterministic hashes before being sent to the model. # # Names and usernames are NOT affected (user-chosen, publicly visible). # # Routing/delivery still uses the original values internally. # redact_pii: false # ============================================================================= # Shell-script hooks # ============================================================================= # Register shell scripts as plugin-hook callbacks. Each entry is executed as # a subprocess (shell=False, shlex.split) with a JSON payload on stdin. On # stdout the script may return JSON that either blocks the tool call or # injects context into the next LLM call. # # Valid events (mirror hermes_cli.plugins.VALID_HOOKS): # pre_tool_call, post_tool_call, pre_llm_call, post_llm_call, # pre_api_request, post_api_request, on_session_start, on_session_end, # on_session_finalize, on_session_reset, subagent_stop # # First-use consent: each (event, command) pair prompts once on a TTY, then # is persisted to ~/.hermes/shell-hooks-allowlist.json. Non-interactive # runs (gateway, cron) need --accept-hooks, HERMES_ACCEPT_HOOKS=1, or the # hooks_auto_accept key below. # # See website/docs/user-guide/features/hooks.md for the full JSON wire # protocol and worked examples. # # hooks: # pre_tool_call: # - matcher: "terminal" # command: "~/.hermes/agent-hooks/block-rm-rf.sh" # timeout: 10 # post_tool_call: # - matcher: "write_file|patch" # command: "~/.hermes/agent-hooks/auto-format.sh" # pre_llm_call: # - command: "~/.hermes/agent-hooks/inject-cwd-context.sh" # subagent_stop: # - command: "~/.hermes/agent-hooks/log-orchestration.sh" # # hooks_auto_accept: false # ============================================================================= # Telemetry # ============================================================================= # Shared metrics are disabled by default. When enabled, Hermes writes only # allowlisted aggregate counters and immutable JSON # packages under $HERMES_HOME/telemetry/shared_metrics; it does not upload them. # Packages include a random profile-scoped ID that stays stable until this # directory is deleted. It is not derived from hardware, account, or host data. # Successfully exported local history is retained for 30 days; pending deltas # are retained until they can be exported. # This profile-owned choice is not overridden by managed-scope configuration. telemetry: shared_metrics: enabled: false # ============================================================================= # Update Behavior # ============================================================================= updates: # Create a full HERMES_HOME zip before every `hermes update`. # Backups land in ~/.hermes/backups/ and can be restored with `hermes import`. # Off by default because large homes can add minutes to every update. pre_update_backup: false # Number of pre-update backup zips to retain. backup_keep: 5 # What non-interactive updates do with local source edits in the Hermes repo. # Interactive terminal updates always prompt before restoring the autostash. # # stash - auto-stash before pull, then auto-restore after success (default) # discard - drop the update-created stash after success; use only on managed # installs where local source edits should not persist non_interactive_local_changes: "stash" # ============================================================================= # Web Dashboard # ============================================================================= # OAuth gate configuration for `hermes dashboard --host `. # The bundled Nous Portal plugin reads these on startup; settings here are # the canonical surface. Each can be overridden by an environment variable: # # dashboard.oauth.client_id <- HERMES_DASHBOARD_OAUTH_CLIENT_ID # dashboard.oauth.portal_url <- HERMES_DASHBOARD_PORTAL_URL # dashboard.public_url <- HERMES_DASHBOARD_PUBLIC_URL # # Env wins when set to a non-empty value. This is what Fly.io's platform- # secret injection uses to push per-deploy client_ids without needing to # bake a config.yaml into the image. Empty env values are treated as unset # so a provisioned-but-not-populated secret can't shadow a valid entry here. # # Local dev / on-prem deploys should typically set these via config.yaml # (the ~/.hermes/.env file is reserved for API keys and secrets). # # dashboard: # oauth: # client_id: "" # agent:{instance_id}; Portal provisions this at deploy # portal_url: "" # blank → default https://portal.nousresearch.com # # # Force the absolute base URL the OAuth callback (and any other public # # URL the dashboard hands to external systems) is built from. Set this # # for deploys behind reverse proxies that don't reliably forward # # X-Forwarded-Host / X-Forwarded-Proto / X-Forwarded-Prefix (manual # # nginx setups, on-prem ingresses, custom-domain Fly deploys without # # full proxy header chains). # # # # When set, the value is the complete authority: scheme + host + # # optional path prefix (e.g. "https://example.com/hermes"). The OAuth # # callback URL becomes "/auth/callback" — X-Forwarded-Prefix # # is IGNORED on this code path because the operator has explicitly # # declared the public URL and we no longer need to guess. # # # # Leave empty to use the existing proxy-header reconstruction (the # # default — works on Fly.io out of the box). # # # # public_url: "https://example.com/hermes" # # # # Reverse proxies connecting from another container or host are not # # trusted by default. Add only the proxy's exact IP address, or a bounded # # CIDR for a dedicated proxy network, so X-Forwarded-Proto and # # X-Forwarded-For can be honored. Loopback is always trusted. Wildcards # # and /0 networks are rejected. # # # # trusted_proxies: # # - "172.20.0.5" # # # - "172.20.0.0/24" # dedicated network, if the IP is dynamic # # ----------------------------------------------------------------------------- # Self-hosted OIDC dashboard auth (generic OpenID Connect — Authentik, # Keycloak, Zitadel, Authelia, Auth0, Okta, Google, …). Use this INSTEAD of the # nous block above when gating the dashboard with your own identity provider. # Each setting can be overridden by an environment variable: # # dashboard.oauth.self_hosted.issuer <- HERMES_DASHBOARD_OIDC_ISSUER # dashboard.oauth.self_hosted.client_id <- HERMES_DASHBOARD_OIDC_CLIENT_ID # dashboard.oauth.self_hosted.scopes <- HERMES_DASHBOARD_OIDC_SCOPES # dashboard.oauth.self_hosted.client_secret <- HERMES_DASHBOARD_OIDC_CLIENT_SECRET # # dashboard: # oauth: # provider: self-hosted # self_hosted: # issuer: "https://auth.example.com/application/o/hermes/" # required # client_id: "hermes-dashboard" # required # scopes: "openid profile email" # optional # # # OPTIONAL — set ONLY if your IDP registered the client as # # *confidential* (Authentik / Keycloak often default to this). When # # set, Hermes authenticates the client at the token endpoint # # (client_secret_basic or client_secret_post, auto-selected from the # # IDP's discovery doc) IN ADDITION to PKCE. Leave unset for a public # # (PKCE-only) client — the common case. # # # # This is a CREDENTIAL: prefer setting HERMES_DASHBOARD_OIDC_CLIENT_SECRET # # in ~/.hermes/.env over putting it here in config.yaml. # # client_secret: "" # ============================================================================= # External secret sources # ============================================================================= # Pull provider credentials from external secret managers at process startup # instead of storing them in ~/.hermes/.env. Only the manager's bootstrap # credential (e.g. BWS_ACCESS_TOKEN / OP_SERVICE_ACCOUNT_TOKEN) lives in .env # (or your shell / desktop session); everything else rotates centrally. # Failures never block startup — Hermes warns once and continues with # whatever .env already had. # # Multiple sources can be enabled at once: # - "mapped" sources (explicit VAR -> ref bindings, e.g. 1Password's env: # map) beat "bulk" sources (whole-project dumps like Bitwarden BSM) # - within a shape, the first source to claim a var wins; later claims # are skipped with a startup warning (never a silent clobber) # - a source's override_existing lets it beat .env/shell values, but # never another secret source's claim # Docs: https://hermes-agent.nousresearch.com/docs/user-guide/secrets/ # # secrets: # # Optional explicit ordering of enabled sources. # # sources: [onepassword, bitwarden] # # # ---- Bitwarden Secrets Manager (bws CLI) -------------------------------- # bitwarden: # enabled: false # access_token_env: BWS_ACCESS_TOKEN # bootstrap token, sourced from .env # project_id: "" # UUID of the BSM project to sync # server_url: "" # "" = US Cloud; EU/self-hosted URL otherwise # cache_ttl_seconds: 300 # 0 disables fresh caching # encrypted_cache: # optional encrypted stale fallback # enabled: false # max_stale_seconds: 0 # 0 disables stale fallback # override_existing: true # BSM values win over existing env # auto_install: true # lazy-download bws into ~/.hermes/bin # # # ---- 1Password (op CLI) ------------------------------------------------- # onepassword: # enabled: false # # Map env-var names to op:// secret references. Each is resolved with a # # single `op read` at startup. # env: # OPENAI_API_KEY: "op://Private/OpenAI/api key" # ANTHROPIC_API_KEY: "op://Private/Anthropic/credential" # account: "" # op --account shorthand; "" = default # service_account_token_env: OP_SERVICE_ACCOUNT_TOKEN # headless auth; unset = desktop session # binary_path: "" # "" = resolve op via PATH; else absolute path # cache_ttl_seconds: 300 # 0 disables BOTH cache layers # override_existing: true # resolved values win over existing env # # # ---- Command helper (any CLI vault) -------------------------------------- # # Run a user-configured helper that prints KEY=VALUE lines on stdout — # # works with any secret store that has a CLI: keepassxc-cli, secret-tool, # # pass, gpg, or a script that cats a tmpfs env file. Composes with the # # sources above (enable any combination). POSIX-only (needs /bin/sh). # # The helper must be fast and NON-interactive (hard timeout, 1 MiB cap); # # its stderr is discarded so diagnostics can't leak secret material. # command: # enabled: false # command: "cat /run/user/1000/hermes-secrets.env" # helper_timeout_seconds: 3 # override_existing: false # .env/shell win by default # # This runs ONCE per process at startup, so it cannot replace a credential # # that expires mid-session. For a single provider whose token needs # # re-minting during a session, use `providers..key_cmd` instead.