Files
aiturk-hermes-ide/cli-config.yaml.example

2148 lines
111 KiB
Plaintext
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Hermes Agent CLI Configuration
# Copy settings from this example into ~/.hermes/config.yaml, or use
# `hermes config set <section.key> <value>` 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"
# Stream API responses from the provider (default: true). The agent core
# prefers streaming for every turn — subagents included — for liveness
# health-checking. Set false to force non-streaming requests for the whole
# session (persists across mid-session model switches). Escape hatch for
# self-hosted OpenAI-compatible servers whose streaming tool-call path is
# broken (e.g. vLLM with --tool-call-parser qwen3_xml + a reasoning parser
# can leak tool calls into plain text instead of returning tool_calls —
# #72901). Orthogonal to display.streaming, which controls token rendering
# only.
# streaming: true
# Azure Foundry keyless auth example:
# provider: "azure-foundry"
# base_url: "https://<resource>.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)
# providers:
# router:
# base_url: https://api.router.com/v1
# api_key: ${RAMP_ROUTER_API_KEY}
# # api_mode auto-detected as codex_responses for api.router.com — the
# # Responses API is Router's native wire (chat/completions is only a
# # compatibility shim). The bundled router provider covers this; a named
# # custom provider is only needed for a non-default Router-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://<workspace>.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://<workspace>.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://<workspace>.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 stay
# opt-in for interactive CLI/TUI/Desktop/ACP sessions, but default on for unattended
# gateway/cron sessions where nobody is present to interrupt a model that
# ignores loop warnings.
tool_loop_guardrails:
warnings_enabled: true
hard_stop_enabled: false
non_interactive_hard_stop_enabled: true
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
# Optional absolute server compaction trigger in input tokens. The default
# null value follows the resolved local compression trigger with an 8192 token
# safety margin. For example, a local trigger of 765000 selects 756808.
# A positive integer stays absolute and only clamps downward when needed so
# the server compacts first. Invalid values use this automatic behavior. If
# the local trigger is unavailable, automatic mode uses 200000.
codex_responses_compact_threshold: null
# 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
# Gateway session-hygiene turn-hold budget (default: 10). Max seconds an
# arriving user turn is held while a still-streaming hygiene summary
# finishes. Distinct from hygiene_timeout_seconds (compressor inactivity
# budget): this bounds user-visible latency so chat transports (Telegram
# ~30s) do not drop a silent connection. On expiry the turn proceeds
# uncompressed; the detached worker keeps its commit admission (when the
# commit is watermark-fenced) and the summary is adopted at the next safe
# boundary. Thinking-model summarizers often need longer than 10s to emit
# the first content token — raise to 300 (or >= your summarizer's real
# time-to-first-content) only if you want THIS turn to wait for the
# compression instead of adopting it one turn late.
hygiene_max_turn_hold_seconds: 10
# 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
# =============================================================================
# Session Storage Retention (state.db)
# =============================================================================
# ~/.hermes/state.db keeps every session, message, and tool call, plus the
# FTS5 search indexes. Since #54189, auto-pruning is ON by default so the file
# stays bounded: at CLI/gateway/cron startup (at most once per
# min_interval_hours) Hermes deletes ENDED sessions whose last activity is
# older than retention_days. Open, pinned, and in-progress sessions are never
# deleted. Stale automation sessions (cron/kanban/subagent/one-shot CLI) whose
# process died without closing them are first *closed*, then aged through a
# further full retention window before removal.
#
# After a prune that removed rows, VACUUM reclaims disk space only when both
# the time throttle (min_vacuum_interval_days) has elapsed AND more than 25%
# of the file's pages are reclaimable — a dense database never pays for a full
# rewrite to reclaim a few MB.
#
# Uncomment to change the defaults shown; set auto_prune: false to keep every
# ended session forever (the pre-#54189 behavior).
# sessions:
# auto_prune: true
# retention_days: 90
# vacuum_after_prune: true
# min_vacuum_interval_days: 30
# min_interval_hours: 24
# Optional direct endpoint for autonomous Bot Mode rooms spanning gateways.
# Leave unset for the safe default: Desktop coordinates cross-gateway rooms and
# same-gateway rooms can still continue on their own. Set this only to the
# public HTTPS base URL that another trusted Hermes gateway can reach. The API
# server key authorizes the initial invitation; scoped grants are signed by a
# separate installation-private secret and survive API-key rotation. Disbanding
# a Group Chat revokes its known peer routes; API-key rotation alone does not.
# Network access should still be limited to the peer gateways (for example with
# your VPN or firewall).
# Plain HTTP is accepted only for loopback testing; every reachable peer URL
# must use HTTPS.
#
# gateway:
# room_link_url: "https://hermes.example.net"
#
# The equivalent environment override is HERMES_ROOM_LINK_URL. After changing
# it, restart the gateway and reopen New Group Chat > Advanced; Hermes will
# re-check the selected gateways automatically.
# 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 5-second default.
# gateway_turn_lease_timeout: 5
# 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
# Transcript-sanitiser repeated-heal escalation (#96870). The pre-send
# sanitiser silently repairs empty non-final turns on the wire copy; after
# this many heal passes within a 10-minute session window Hermes logs one
# ERROR (with session id + heal pattern) and sends a ONE-TIME out-of-band
# notice to the user suggesting /debug share or `hermes doctor`. The notice
# goes through the status channel only — conversation context and prompt
# caching are untouched. Set 0 to disable escalation (per-window WARNINGs
# still fire). Default 3.
# sanitizer_heal_escalation_threshold: 3
# 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: {}
# Fast mode (OpenAI Priority Processing / xAI Grok 4.6 / Anthropic Fast Mode
# on Opus 4.8+). Premium pricing; only sent to first-party endpoints.
# "" / "normal" - off (default)
# "fast" - every request
# "auto" - only the first fast_auto_seconds of every turn
# "cold" - that window on the first turn of a session only
# Also: /fast normal|fast|auto|cold [--global]
service_tier: ""
fast_auto_seconds: 60
# 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"
# =============================================================================
# Gateway Lifecycle
# =============================================================================
gateway:
# Post-interrupt grace for running agents during an unexpected SIGTERM.
# Adapter, bridge, and database teardown starts after this many seconds even
# if an agent has not unwound. Keep it below the service-manager stop budget.
# signal_interrupt_grace_timeout: 1
# Let platform adapters honor HTTP_PROXY / HTTPS_PROXY / NO_PROXY (and
# SSL_CERT_FILE) from the process environment, plus macOS system-proxy
# auto-detection. Set to false when the gateway inherits a proxy it must not
# use (e.g. a Windows Scheduled Task picking up a local Clash/V2Ray proxy that
# isn't running). Explicit per-platform vars like DISCORD_PROXY still apply.
# trust_env: true
# =============================================================================
# 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 is applied across core + plugin + skill commands before
# # the cap, so a listed skill command always keeps a menu slot.
# priority:
# - my_plugin_command
# - my-important-skill
# 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
# # Slack user IDs whose Web-API posts (user token, e.g. your own
# # dashboard/mobile front-end) count as human instead of being dropped
# # as app traffic. Narrower than allow_bots: all. Users only — never apps.
# api_human_users: ["U0AAAAAAA", "U0BBBBBBB"]
# # 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.<name>``
# 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 <interrupt|queue|steer>.
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
# Play terminal bell when a blocking prompt opens and waits on you:
# clarify questions, dangerous-command approvals, sudo password, secret
# capture. Same mechanism as bell_on_complete (\a) — works over SSH.
# true: Ring whenever the agent is waiting for your input
# false: Silent (default)
bell_on_prompt: 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 <name>.
#
# 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/<name>.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).
#
# An alias pointing at its own endpoint can carry that endpoint's
# credential with `api_key` (a literal, or a "${VAR}" reference) or
# `key_env` (an env var name). `api_key` wins if both are set. When
# neither is set the key is resolved from the alias HOST — never from
# whatever provider was active before the switch, which would send that
# provider's secret to an unrelated third party.
#
# 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"
# theta:
# model: theta-1
# provider: custom
# base_url: "https://theta.example.com/v1"
# key_env: THETA_API_KEY # or: api_key: "${THETA_API_KEY}"
# =============================================================================
# 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.
# 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.
#
# Nothing is uploaded unless you also set `send: true`. That is a separate
# opt-in and requires `enabled`; it never turns collection on by itself.
# When sending is on:
# * only packages whose entire collection period falls within one
# continuous recorded consent window are ever sent. Consent windows open
# when you enable sending and close when you disable it, so data
# collected before you opted in — or during any gap between opt-ins —
# stays on this machine;
# * each package carries the profile-scoped ID as-is. It is a random UUID
# with no hardware, account, or host-derived content, and deleting the
# shared-metrics directory resets it.
# See docs/observability/relay-shared-metrics.md (Appendix A) for the full
# consent, identity, retention, and deletion decisions.
telemetry:
shared_metrics:
enabled: false
send: false
# endpoint: https://telemetry.nousresearch.com/v1/telemetry
# =============================================================================
# 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 <non-loopback>`.
# 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 "<public_url>/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.<name>.key_cmd` instead.